0

We have ASP.NET WEB API server box generating JWT tokens for authenticated users. The server is using a secret: SECRET. These tokens are sent back to our UI, all the API calls are presented with this token.

Recently we started writing our newer services in nestjs and are using @nestjs/passport, passport-jwt libraries for authorization. Our UI will start hitting these new APIs and hence we wanted to authorize our users by the same token generated by our ASP.NET WEB API servers. I thought this will be straight forward since I only have to decode the token using the same secret: SECRET. But I keep getting 401 from nestjs after setting up the jwt strategy.

Tried creating a token from nestjs using the same SECRET and consuming it, and that worked. But the token generated by the ASP.NET servers are not working.

Method from ASP.NET WEB API that creates the token: (note: key = SECRET)

public string CreateToken(string payload) {                       
    var encoding = new System.Text.ASCIIEncoding();

    string unsignedToken =_header + tokenSeperater + payload;
    string signature;

    using (var hmacsha256 = new HMACSHA256(encoding.GetBytes(key))) {
        byte[] hashmessage = hmacsha256.ComputeHash(encoding.GetBytes(unsignedToken));
        signature=Convert.ToBase64String(hashmessage);
     }
     string payloadEncoded = UTF8Bas64Encode(payload);
     string token = "Bearer " + _headerEncode + tokenSeperater  + payloadEncoded + tokenSeperater  + signature;

     return token;
}

NestJS Passport setup: (Note: jwtConfig.secret = SECRET)

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      publicKey: jwtConfig.secret,
      signOptions: {
        expiresIn: jwtConfig.expiresIn,
      },
    })
  ],
})
export class AuthModule {}

Strategy:

export class JwtStrategy extends PassportStrategy(Strategy) {

  constructor(
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: config.get('jwt.secret')
    });
  }

  async validate(payload: JwtPayload) {

    const user = <find user>;
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

This is the token from our auth server:

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiAiNDE3MSIsIlNlc3Npb25JZCI6ICI2Yjg2M2I2NS02YTM1LTQ1NDQtOTllNy0yMmJjMzEzY2M1YTMifQ==.bu+GZ+tq+Wc1Srvp/0u7jMvqBnDJqd2N5xWx2dItxlA=

Expected to decrypt the token but getting 401 very early in the lifecycle of the call.

jps
  • 20,041
  • 15
  • 75
  • 79
user898788
  • 109
  • 7

2 Answers2

1

This is correct. I managed to debug the tokens using the jws module used internally by NestJS, and found out that the following line in jws library was failing:

var JWS_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/;

function isValidJws(string) {
  return JWS_REGEX.test(string) && !!headerFromJWS(string);
}

Further when I looked at the token, I found characters not in the Base64 list(https://en.wikipedia.org/wiki/Base64).

For C#, I switched to System.IdentityModel.Tokens.Jwt class and it worked. This class does the encoding and necessary replacement of characters as per the spec so I would recommend others to use it. Until now our JWT tokens weren't used for claims outside our system and hence the problem went unnoticed.

user898788
  • 109
  • 7
0

Your code to create the token in ASP.NET is wrong, because you only use base64 encoding. The result is a token that contains reserved characters (+ / =)

JWT uses base64Url encoding (see https://www.rfc-editor.org/rfc/rfc7519#section-3), which avoids these characters. These are are reserved characters for URIs (see section 2.2. in https://www.ietf.org/rfc/rfc2396.txt) and are not allowed in a JWT, because sometimes tokens are transmitted as a parameter in the url.

The recommended solution would be to use one of the JWT libraries. In your code, you're doing things manually (which is good for learning purposes), so to coninue this way, you need to convert the base64 encoded result to base64urlencoding.

This can be done with string.Replace:

string payloadEncoded = UTF8Bas64Encode(payload).TrimEnd('=').Replace('+', '-').Replace('/', '_');

And the same of course for the header and signature!

Community
  • 1
  • 1
jps
  • 20,041
  • 15
  • 75
  • 79