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.
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 base64url
encoding.
This can be done with string.Replace
:
string payloadEncoded = UTF8Bas64Encode(payload).TrimEnd('=').Replace('+', '-').Replace('/', '_');
And the same of course for the header and signature!