12

I'm using jsonwebtoken to decode a token, and I'm trying to get the expiration date. Typescript is throwing errors regarding the exp property, and I'm not quite sure how to solve them:

import jwt from 'jsonwebtoken'

const tokenBase64 = 'ey...' /* some valid token */

const token = jwt.decode(tokenBase64)
const tokenExpirationDate = token.exp
//                                ^^^
// Property 'exp' does not exist on type 'string | object'. Property 'exp' does not exist on type 'string'.

I have installed @types/jsonwebtoken, and looked for a token type to cast token, but did not find any. Suggestions?

Using

  • @types/jsonwebtoken@7.2.3
  • jsonwebtoken@8.1.0

.tsconfig:

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": ".",
    "jsx": "Preserve",
    "moduleResolution": "Node",
    "module": "ESNext",
    "sourceMap": true,
    "removeComments": true,
    "allowSyntheticDefaultImports": true,
    "target": "ESNext"
  }
}
aryzing
  • 4,982
  • 7
  • 39
  • 42

9 Answers9

14

This is how I am using decode with TS

import jwt from 'jsonwebtoken';

export const isTokenExpired = (token: string): boolean => {
    try {
        const { exp } = jwt.decode(token) as {
            exp: number;
        };
        const expirationDatetimeInSeconds = exp * 1000;

        return Date.now() >= expirationDatetimeInSeconds;
    } catch {
        return true;
    }
};

Not needed but here you go as well

import 'jest';
import jwt from 'jsonwebtoken';

import { isTokenExpired } from 'path-to-isTokenExpired/isTokenExpired';

describe('isTokenExpired', () => {
    it('should return true if jwt token expired', () => {
        const currentTimeInSecondsMinusThirtySeconds = Math.floor(Date.now() / 1000) - 30;
        const expiredToken = jwt.sign({ foo: 'bar', exp: currentTimeInSecondsMinusThirtySeconds }, 'shhhhh');

        expect(isTokenExpired(expiredToken)).toEqual(true);
    });

    it('should return false if jwt token not expired', () => {
        const currentTimeInSecondsPlusThirtySeconds = Math.floor(Date.now() / 1000) + 30;
        const notExpiredToken = jwt.sign({ foo: 'bar', exp: currentTimeInSecondsPlusThirtySeconds }, 'shhhhh');

        expect(isTokenExpired(notExpiredToken)).toEqual(false);
    });

    it('should return true if jwt token invalid', () => {
        expect(isTokenExpired('invalidtoken')).toEqual(true);
    });
});
JSGuru
  • 269
  • 2
  • 7
9

I got the same error message when I used the line import jwt from 'jsonwebtoken' With var jwt = require('jsonwebtoken'); [1] instead it works fine:

var jwt = require('jsonwebtoken');
var tokenBase64 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMTUxMTk1MDcwMyIsImFkbWluIjp0cnVlfQ.wFC-9ZsqA9QuvLkRSkQmVYpUmgH9R-j8M4D0GECuPHY';

const token = jwt.decode(tokenBase64);
const tokenExpirationDate = token.exp
console.log(tokenExpirationDate);

[1] see also https://github.com/auth0/node-jsonwebtoken

Petruza
  • 11,744
  • 25
  • 84
  • 136
jps
  • 20,041
  • 15
  • 75
  • 79
  • thanks for updating your question. The problem is caused by `import jwt from 'jsonwebtoken'`. I updated my answer accordingly. Hope it will work for you. If you get an error that require can't be found do `npm install @types/node --save-dev`, see https://stackoverflow.com/questions/31173738/typescript-getting-error-ts2304-cannot-find-name-require – jps Nov 30 '17 at 21:44
  • 5
    This works, but it would be nice if anyone has a solution where one could still use import instead of require. Perhaps the typings file should be altered. – Robin van der Knaap Mar 02 '18 at 19:48
9

The only way I found to user import is:

import { sign, verify } from 'jsonwebtoken';
sign('Hello', 'secret');

But I think the require method is better so that you don't have to explicitly import every single function.

user3582315
  • 201
  • 3
  • 8
  • How about this error after implementing your answer? `Argument of type 'string | undefined' is not assignable to parameter of type 'Secret'`' Any ideas? – chris Apr 17 '20 at 04:24
  • 1
    @chris The only way I found to get rid of ```ts Argument of type 'string | undefined' is not assignable to parameter of type 'Secret ``` was to create my own type definitions. With regards to the secret key I had to do the following ```ts // eslint-disable-next-line no-unused-vars declare namespace NodeJS { export interface ProcessEnv { JWT_SECRET: string, JWT_EXPIRATION: string, TOKEN_ISSUER: string } } ``` – Wayne Links Jul 15 '20 at 11:46
6

As of jsonwebtoken 8.3, jsonwebtoken.decode() has the following type definitions:

export function decode(
    token: string,
    options?: DecodeOptions,
): null | { [key: string]: any } | string;

Since Typescript cannot infer the correct type and exp is not known, the simplest way out is to cast the result to any.

import jwt from 'jsonwebtoken'

const tokenBase64 = 'ey...' /* some valid token */

const token: any = jwt.decode(tokenBase64)
const tokenExpirationDate = token.exp
paulboony
  • 188
  • 2
  • 4
  • 2
    I am having this issue: Module '"./node_modules/@types/jsonwebtoken/index"' has no default export. – Alan Apr 16 '20 at 17:30
4

I think import * as jwt from 'jsonwebtoken'; should work as expected.

Elias
  • 515
  • 5
  • 10
2
import * as jwt from 'jsonwebtoken'

const { authorization } = ctx.req.headers
const token = authorization.replace('Bearer ', '')
const decoded = jwt.verify(token, 'APP_SECRET')
const userId = (decoded as any).userId

Of course you can type decoded the way you use the token instead of any

Alan
  • 9,167
  • 4
  • 52
  • 70
2

The return type of jwt.verify and jwt.decode is 'string | object'.

In your case, you have some additional information that Typescript does not have about the type of the return type. So you can add it like this:

const token = jwt.decode(tokenBase64) as {exp: number}
const tokenExpirationDate = token.exp

Of course you can add any other value in the object as well.

While it's reasonable to assume that exp is present, other keys might not be present. Make sure that the token you are decoding actually includes them or add it as an optional value: ({exp: number; random?: string})

pa1nd
  • 392
  • 3
  • 16
0

I found myself creating a helper for this (class based solution - can be used as separate function of course):

import { JwtPayload, verify } from "jsonwebtoken";

export class BaseController {
  // ...
  static decodeJWT = <T extends { [key: string]: any }>(token: string) => {
    return verify(token, process.env.JWT_ACCESS_TOKEN!) as JwtPayload & T; 
    // this typing allows us to keep both our encoded data and JWT original properties
  };
}

used in controllers like:

import { BaseController } from "./BaseController";

export class UserController extends BaseController {
  static getUser = async (
    // ...
  ) => {
    // get token
    // username may or may not be here - safer to check before use
    const payload = this.decodeJWT<{ username?: string }>(token); 
    // no error here, we can extract all properties defined by us and original JWT props
    const { username, exp } = payload;
    // do stuff...
  };
}
Emzaw
  • 618
  • 6
  • 11
0

You can do it like this:

import jwt, { JwtPayload } from 'jsonwebtoken'

const tokenBase64 = 'ey...' /* some valid token */

const token = jwt.decode(tokenBase64) as JwtPayload
const tokenExpirationDate = token.exp
//                                ^^^
// token is now seen as a JwtPayload instance and you can use it's properties such as 'exp'.
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Ronner
  • 1
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 14 '23 at 09:41