I'm trying to slowly migrate to TypeScript because I consider it a better alternative than plain old JavaScript.
There might be a major drawback (that I realised now) with it - not all attributes are typed.
For example:
In my NodeJS project I want to use the npm package jwt-decode. It has this .d.ts file:
export class InvalidTokenError extends Error {}
export interface JwtDecodeOptions {
header?: boolean;
}
export interface JwtHeader {
type?: string;
alg?: string;
}
export interface JwtPayload {
iss?: string;
sub?: string;
aud?: string[] | string;
exp?: number;
nbf?: number;
iat?: number;
jti?: string;
}
export default function jwtDecode<T = unknown>(
token: string,
options?: JwtDecodeOptions
): T;
For this piece of code:
var decoded: JwtHeader & JwtPayload = jwtDecode(bearerToken);
console.log(decoded);
I get this object:
{
exp: 1645637327,
iat: 1645601327,
jti: 'removed',
iss: 'http://localhost:8080/auth/realms/supercatalog',
aud: 'account',
sub: 'removed',
typ: 'Bearer',
azp: 'restapi',
session_state: '2434f33d-73c4-4f38-8c80-e92356380ffa',
acr: '1',
'allowed-origins': [ '' ],
realm_access: {
roles: [
'app-elev',
'offline_access',
'uma_authorization',
'default-roles-supercatalog'
]
},
resource_access: { restapi: { roles: [Array] }, account: { roles: [Array] } },
scope: 'email profile',
sid: 'removed',
email_verified: false,
preferred_username: 'elev'
}
I need to get access to resource_access -> restapi -> roles.
Now, the problem is that it is not typed and I can't access it. If I try to console.log(decoded.resource_access) it won't work.
So, my question is as follows:
How would you manage this situation? What's the solution?
I'm thinking of:
creating another interface which implements JwtPayload but has more attributes that I need - I don't like this, it would be very time-consuming to do that each time, I think
use decoded as a plain JS object and access anything I want easily - I also don't like this, it defeats the whole purpose of TS.
How can a situation like this be managed?
Thanks.
// describe your custom fields
interface MyJwt extends JwtHeader, JwtPayload {
realm_access: {
roles: string[] // a union of known types would be more relevant
// provided that it is a fixed list
}
}
// as a good practice don't use var, but const
// and if it appears that you have to change it, switch to let
const decoded = jwtDecode<MyJwt>(bearerToken);
// or :
const decoded: MyJwt = jwtDecode(bearerToken);
// decoded has the expected type
console.log(decoded.realm_access.roles);