1

I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt for my .NET Core project.

Whenever I generate a new token I store that to the database. First of all this is how I generate a new token

    public string GenerateToken(Dictionary<string, object> payload)
    {
        DateTime tokenExpiresAt = DateTime.Now.AddMilliseconds(1); // from config
        byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
        SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
    
        SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
        {
            Claims = payload,
            Expires = tokenExpiresAt,
            SigningCredentials = new SigningCredentials(symmetricSecurityKey, 
                SecurityAlgorithms.HmacSha256Signature)
        };
    
        JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
        SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
        string token = tokenHandler.WriteToken(securityToken);

        return token;
    }

The generated sample token is

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI

If a client tries to access a protected endpoint the default configuration will handle the basic validation (configured in the DI setup in the Startup file)

        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(jwtBearerOptions =>
            {
                byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
                SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
                
                jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuerSigningKey = true,
                    ValidateLifetime = true,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    IssuerSigningKey = symmetricSecurityKey,
                };

                jwtBearerOptions.Events = new JwtBearerEvents()
                {
                    OnTokenValidated = ProcessAfterTokenValidation
                };
            });

As you can see I added a method to the OnTokenValidated event listener. This method should check, if the token exists in the database.

    public async Task ProcessAfterTokenValidation(TokenValidatedContext tokenValidatedContext)
    {
        JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
        string token = jwtSecurityTokenHandler.WriteToken(tokenValidatedContext.SecurityToken);
        
        // ... check if token exists in db ...
    }

The problem with that method is that the generated token is not the exact token as it is stored in the database. There is missing a last segment. The token variable holds this

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.

and the missing last part would be

cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI

Does someone know why the last part of the token is missing?

Thanks for help!

Question3r
  • 2,166
  • 19
  • 100
  • 200

1 Answers1

2

The last part of the token is the signature. The purpose of the OnTokenValidated method is to provide a ClaimsIdentity. The signature is not a component of the claims of the token holder.

If you had a key rotation policy on the issuing side a given user could present identical claims with a different signature before and after a key rotation.

The identity of the user is derived from a combination of the issuer plus any claims that identify the user (e.g. username in the token from your example).

In your case, since you are the issuer, the token minus the signature simply represents proof that the user has successfully authenticated against your token issuing endpoint. The claims within the token should lead to a database record rather than the token itself.

bpdohall
  • 1,046
  • 6
  • 9
  • 1
    thanks for your reply. What I understood: I can either store the token without that signature to the database or add the signature to the token string during the token validation. What I didnt get how to get the signature (maybe as a string) for any of both sides – Question3r Jul 23 '20 at 04:56
  • what I would like to know is that if I can fetch the signature from somewhere to add it to the auth token or subtract it from the database token or subtracting the signature from the generated token :) – Question3r Jul 23 '20 at 05:06
  • 1
    if you insist on storing the token in the database, chop off the segment after the last period. I suggest not storing the token at all. For reference: https://stackoverflow.com/questions/42763146/does-it-make-sense-to-store-jwt-in-a-database – bpdohall Jul 23 '20 at 19:50
  • 1
    I'm storing tokens to the db to make sure that if a user signs out, every token from him gets removed from the db and if someone uses his token he will still get a 401 because its not valid anymore. do you have any better ideas to invalidate a token? – Question3r Jul 24 '20 at 05:06
  • 1
    and there is no way to generate the signature instead of chopping off the string? I know that each segment comes with a "." but I'm not sure if splitting the string by this character is a clean way – Question3r Jul 24 '20 at 05:07
  • When an authenticated user invokes a logout, a timestamp is persisted to the DB. Any token having an issued time earlier than the most recent logout is rejected. – bpdohall Jul 25 '20 at 05:45
  • ah yeah, I found exactly that solution here https://stackoverflow.com/questions/63068463/only-store-the-time-of-the-jwt-with-the-highest-lifetime-to-the-database-instead :) – Question3r Jul 25 '20 at 07:45