How to add additional properties to JwtPayload type from @types/jsonwebtoken
Asked Answered
G

4

13

I am new to typescript and experimenting with porting an express app to use typescript. The server uses JWTs for authentication/authorisation and I have a utility function that decodes and verifies a given token. The function is wrapped in a promise so I can use async/await in the middleware that implements it.

import httpError from 'http-errors';
import jwt from 'jsonwebtoken';

const { ACCESS_TOKEN_SECRET } = process.env;

export function verifyAccessToken(token: string): Promise<jwt.JwtPayload | undefined> {
  return new Promise((resolve, reject) => {
    jwt.verify(token, ACCESS_TOKEN_SECRET as string, (err, payload) => {
      if (err) {
        return reject(new httpError.Unauthorized());
      }
      return resolve(payload);
    });
  });
}

This function works fine, however I have additional information in the JWT. Specifically I have a role property, thus the payload is of type:

{
  sub: string,  // ID issued by mongoose
  role: string, // My new information that is causing error
  iat: number,
  exp: number
}

My problem is that the type for JwtPayload from @types/jsonwebtoken does not contain role hence when the Promise resolves, I get a typescript error when trying to access payload.role in the authentication middleware.

import { RequestHandler } from 'express';
import httpError from 'http-errors';
import { verifyAccessToken } from '../utils'

export const authenticate: RequestHandler = async (req, res, next) => {
  try {
    const authHeader = req.headers['authorization'] as string;
    if (!authHeader) {
      throw new httpError.Unauthorized();
    }

    const accessToken = authHeader.split(' ')[1];
    if (!accessToken) throw new httpError.Unauthorized();

    const payload = await verifyAccessToken(accessToken);
// If I try to access payload.role here I get an error that type JwtPayload does not contain 'role'

    next();
  } catch (err) {
    next(err);
  }
};

How do I extend the JwtPayload type to add the role property? I have tried to define my own custom type and completely override the type returned from jwt.verify() but this throws an error that no overload matches this call.

interface MyJwtPayload {
  sub: string;
  role: string;
  iat: number;
  exp: number;
}

// ... then in the utility function replace jwt.verify() call with
jwt.verify(token, ACCESS_TOKEN_SECRET as string, (err, payload: MyJwtPayload) => {

Thanks.

Graner answered 16/7, 2021 at 5:35 Comment(0)
R
5

You should be able to achieve this via declaration merging.

Somewhere in your code add this:

declare module "jsonwebtoken" {
    export interface JwtPayload {
        role: string;
    }
}

This should extend the interface as you want it.

Roseanneroseate answered 18/7, 2021 at 16:58 Comment(3)
Thanks! I had tried this, however I didn't put " " around the module name. Hence it didn't carry and I didn't think it was the right thing to do. Cheers.Graner
This only works if you only need to parse one type of JwtPayload right?Pyxie
@Pyxie if you have several types you could use a union type.Bilestone
S
12

redeclare the jsonwebtoken module with an extended Payload, then parse/cast the verified token accordingly.

import * as jwt from 'jsonwebtoken'

declare module 'jsonwebtoken' {
    export interface UserIDJwtPayload extends jwt.JwtPayload {
        userId: string
    }
}

export const userIdFromJWT = (jwtToken: string): string | undefined => {
    try {
        const { userId } = <jwt.UserIDJwtPayload>jwt.verify(jwtToken, process.env.JWT_COOKIE_SECRET || 'MISSING_SECRET')

        return userId
    } catch (error) {
        return undefined
    }
}
Souter answered 3/8, 2021 at 18:56 Comment(0)
R
5

You should be able to achieve this via declaration merging.

Somewhere in your code add this:

declare module "jsonwebtoken" {
    export interface JwtPayload {
        role: string;
    }
}

This should extend the interface as you want it.

Roseanneroseate answered 18/7, 2021 at 16:58 Comment(3)
Thanks! I had tried this, however I didn't put " " around the module name. Hence it didn't carry and I didn't think it was the right thing to do. Cheers.Graner
This only works if you only need to parse one type of JwtPayload right?Pyxie
@Pyxie if you have several types you could use a union type.Bilestone
F
0
import { Jwt } from 'jsonwebtoken';

export type Token = Jwt;
Filament answered 18/3, 2023 at 19:5 Comment(0)
B
0

Here is how you do it for Nextjs and Typescript

import * as jwt from 'jsonwebtoken';

export const verifyToken = async (token: string): Promise<string | false> => {
    try {
        const decoded = await (<jwt.JwtPayload>(
            jwt.verify(token, process.env.JWT_SECRET!)
        ));

        return decoded.role!;
    } catch (error: any) {
        return false;
    }
};
Bozovich answered 11/10, 2023 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.