jwt.verify(token, secret, (error, data) => {
const { userName } = data
})
throws
Property 'userName' does not exist on type '{}'.
"@types/jsonwebtoken": "^8.5.0",
"jsonwebtoken": "^8.5.1",
This is the current typings for VerifyCallback
export type VerifyCallback = (
err: VerifyErrors | null,
decoded: object | undefined,
) => void;
Maybe a generic could be added to extend decoded.
Or someone else has a better idea how to improve TypeScript behaviour here?
My suggestion would be something like
export function verify<Decoded>(
token: string,
secretOrPublicKey: Secret | GetPublicKeyOrSecret,
callback?: VerifyCallback<Decoded>,
): void;
export type VerifyCallback<Decoded> = (
err: VerifyErrors | null,
decoded: Decoded | undefined ,
) => void;
Any suggestions on this?
You can't actually type the decoded value, as it comes from an untrusted source; typing it as anything other than Record<string, unknown> would be giving you a false sense of security in your typesafety.
Even if you think the decoded token will include a given property, it may not actually, JWTs don't validate properties beyond a limit set (those you can validate using the options argument).
There's nothing that guarantees the returned decoded value will contain given properties — only those the library explicitly checks (based on the options argument) could be "known" types. decoded would be better represented as Record<string, unknown>
So if you need a specific property, you should actually check if the decoded JWT contains that property.
Example:
const token = jwt.sign({ foo: "fooValue"}, "secret");
jwt.verify<{ bar: boolean }>(token, "secret", (err, decoded) => {
// decoded is: { foo: "fooValue" }, so if `decoded` was cast as `{ bar: boolean }` then
// you'd think `decoded.bar` is always present. As the signer of the token's payload
// didn't include the `bar` property, then it won't be there.
})
If you want to guarantee the shape of the decoded object, then you'd need to pass decoded through a validation library like Joi or Yup, or just manually assert properties exist.
This issue can be a particular gotcha for people consuming JWTs from other services/servers.
In fact, in the typings for jws.decode which this module uses, payload of jws.Signature is typed as any.
There's actually no code in jwt.decode that guarantees the decoded value will be an object: https://github.com/auth0/node-jsonwebtoken/blob/master/decode.js#L7-L17
Nor in jwt.verify — payload isn't guaranteed to a parsed JSON Object (it comes from jwt.decode): https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js#L136
To clarify with working code:
const jws = require('jws');
const jwt = require('jsonwebtoken')
const token = jws.sign({ header: { "alg": "HS256" }, payload: true, secret: 'secret'})
// => 'eyJhbGciOiJIUzI1NiJ9.dHJ1ZQ.tNWhzx87CKMgFxL2x9cqZJkzixGWWAkRNbHmbn6d5Dk'
jwt.decode(token, { complete: true })
/* {
header: { alg: 'HS256' },
payload: 'true',
signature: 'tNWhzx87CKMgFxL2x9cqZJkzixGWWAkRNbHmbn6d5Dk'
} */
jwt.verify(token, "secret")
// => 'true'
This is also why you should be really protective over your jsonwebtoken secret/key data, if that gets compromised, then someone can create all manner of random crap to exploit bugs in your app.
Most helpful comment
My suggestion would be something like