-1

First some code... I have a Security class:

public static class Security
{
    public static RSACryptoServiceProvider RSA { get; } = new(4096);
    public static SigningCredentials Credentials()
    {
        return new SigningCredentials(new RsaSecurityKey(RSA), SecurityAlgorithms.RsaSha512)
        {
            CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
        };
    }

}

This is a simple static class that generates an RSA key and related signing credentials. It has some more code but that's not important for my question...
Then I have code to generate a JWT token using System.IdentityModel.Tokens.Jwt and System.Security.Claims:

    var claims = new[]
    {
        new Claim(JwtRegisteredClaimNames.Jti, account.Id.ToString()),
        new Claim(JwtRegisteredClaimNames.Sub, account.Name),
        new Claim(JwtRegisteredClaimNames.Name, account.FullName),
        new Claim(JwtRegisteredClaimNames.GivenName, account.FirstName),
        new Claim(JwtRegisteredClaimNames.FamilyName, account.LastName),
        new Claim(JwtRegisteredClaimNames.Birthdate, account.Birthdate.ToString()),
        new Claim(JwtRegisteredClaimNames.Email, account.Email.ToString()),
    };
    var token = new JwtSecurityToken
    (
        issuer: host,
        audience: audience,
        claims: claims,
        notBefore: DateTime.UtcNow,
        expires: DateTime.UtcNow.AddHours(4),
        Security.Credentials()
    );

Again, not too challenging. A few claims from data in my code, which is my own class with basic user data. And this is turned into a token that is signed. It results in this token:

eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhYmQ3NjUxMS1jNmNmLTQzZjUtOGE1Zi1iZjEzZTg0NGM1ZmEiLCJzdWIiOiJ3aW0iLCJuYW1lIjoiV2ltIFRlbiBCcmluayIsImdpdmVuX25hbWUiOiJXaW0iLCJmYW1pbHlfbmFtZSI6IlRlbiBCcmluayIsImJpcnRoZGF0ZSI6IjExLTctMTk2NiIsImVtYWlsIjoid2ltQGV4YW1wbGUuY29tIiwibmJmIjoxNjQwNjU1MDA1LCJleHAiOjE2NDA2Njk0MDUsImlzcyI6ImxvY2FsaG9zdDo3MTkwIiwiYXVkIjoiL1Rva2VuL0xvZ2luIn0.iFn6DKvHxW_Vd6bxlLs4quQwCyYWpL7bbyMJSTVMxS4RRQ9GxM9IvgoymEqyn9YNBc68TFULNPRVBFE8_mF-IpYt1NIGtp8p7u9laMRegEfXyvJFislDcPZoMbD5xjB18xwUwTpkNjdX4svzFIR8mQT5bLcb5BojFLhxUVT-oJ8G_f0lD0XWJkDo40OW9aHECjIOOu_KfeYZiPdiuo-q9yed6WN1-dgqz4ykWOYC7FoMA0ZYogG5pRvmVLOq8haiEDEJRszbhjj6CLt6h_hlwOS0mMZF6a7Ag5mN4lI2UtEot-jYyXM72eXgBPm_bWNsa6RG_m9vFf4EElK4ekfFd3V_qkAnZ8XEhfTcRD8ASEVjXg8hTI5SY4LFyrggso44HFN5oEoVWlVVcGZxEG2R6LspJSFD4xkrVUUeQvt7IbBL1ViflSfGYp6-cLMPfxo3qZvItC7DxP_8aAygNmHi0_T8BxGNLnOwqrPtDOjKzw4SqEEwBf5S5R6O_dufhauAVvvS_rKQnkEdq-MNNNW5U90ytauCcAgFEGa03MN3E4hhQKUE5y70wCStrIFcBtJezf32U8V7SHv1WrVMpnQXxQoLneZh3b7QsthKCh49ypUSLjw4yL9VVur2K0oV8NsLnwoqH5dCVFuq7YHmreiTa-ZUIFVrgWYMzMqR8hG7Ato

And now I want to convert it back to a token by using this code:

var tokenData = new JwtSecurityToken(tokenString);

And that will generate the token from the string again, but it's not verified, of course. And I need to verify the signature for this token.
To be clear: this is NOT for an ASP.NET Core application but I do use .NET Core for this console application. This is a simple tool that connects to a database which contains tokens as strings and it just has to validate every token inside it with a specific RSA key. (Also stored in a safe location.) Tokens in this database come from a web application which uses this table for logging purposes but apparently some tokens get signed with a wrong or invalid key and I need a quick way to find which tokens are invalid in this log.

So, what is the easiest way to validate these signatures?


And an update: My code is using Microsoft.AspNetCore.Authentication.JwtBearer and for some reason it caused some unexpected behavior and errors during validations. When I kicked it out and replaced it with System.IdentityModel.Tokens.Jwt it all starts to work.
Both packages are very similar in classes and other parts, and I'm not sure how they differ from one another, but they do differ. One is to work with OpenID and the other one for JWT in general. Switching to the last one solved my problems...

Wim ten Brink
  • 25,901
  • 20
  • 83
  • 149
  • FYI, Google shows up way too many options going back all the way to 2011 and many use obsolete functions or third-party packages that don't work so well anymore. (Or are obsolete or not for .NET core...) – Wim ten Brink Dec 28 '21 at 01:46
  • Does this answer your question? [c# How to verify signature JWT?](https://stackoverflow.com/questions/36171656/c-sharp-how-to-verify-signature-jwt) – Progman Dec 28 '21 at 12:33
  • @Progman No, It's an old solution that predates .NET Core and doesn't work. Plus, it feels af if an easier method should exist by now. – Wim ten Brink Dec 28 '21 at 23:40
  • 1
    Please [edit] your question to include a MCVE of the problem you have. Using `JwtSecurityTokenHandler.ValidateToken()` works just fine, see https://dotnetfiddle.net/8GGlR4 – Progman Dec 29 '21 at 00:30
  • @Progman The question already has enough as example. As for that .NET Fiddle link: I get this: Method not found: 'Void Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(Microsoft.IdentityModel.Tokens.SecurityToken,System.Nullable`1,System.Nullable`1,System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,System.Text.StringBuilder)'. *So why this exception?* – Wim ten Brink Dec 29 '21 at 02:33
  • @Progman As it turns out, I was using the JwtBearer library while I should have used the Tokens.JWT library, as explained in the update. Your example made me think why yours worked while mine didn't. Both packages use very similar classes, making it confusing... – Wim ten Brink Dec 29 '21 at 03:02

2 Answers2

0

As it turns out, it seems to be a bug in package Microsoft.AspNetCore.Authentication.JwtBearer but the package System.IdentityModel.Tokens.Jwt is near-identical but got the job done. The following code just works:

        var param = new TokenValidationParameters
        {
            ClockSkew = TimeSpan.FromMinutes(5),
            ValidateAudience = true,
            ValidateIssuer = true,
            ValidateIssuerSigningKey = true,
            ValidateTokenReplay = false,
            ValidateLifetime = true,
            IssuerSigningKey = new RsaSecurityKey(Security.RSA),
            ValidAudience = Audience,
            ValidIssuer = Issuer,
        };
        var claim = new JwtSecurityTokenHandler().ValidateToken(tokenString, param, out _);
        if (claim == null) throw new InvalidSignature(tokenString);

(But with the Bearer library ir failed. Not sure why.)

Wim ten Brink
  • 25,901
  • 20
  • 83
  • 149
  • "Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result". https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytokenhandler.validatetokenasync?view=msal-web-dotnet-latest#system-identitymodel-tokens-jwt-jwtsecuritytokenhandler-validatetokenasync(system-string-microsoft-identitymodel-tokens-tokenvalidationparameters) – CatTrain Jun 21 '23 at 02:54
-1

If you're not limited to the language, you can use a library that we created at Curity for validating JWTs: https://www.npmjs.com/package/@curity/jwt-validation. We've also created a list of libraries which you can use to validate JWTs: https://curity.io/resources/guides/libraries/api/ so if you want to stick to .NET you can find a library there. We also have a tutorial which shows how to use that .NET lib: https://curity.io/resources/learn/dotnet-api/

Dharman
  • 30,962
  • 25
  • 85
  • 135
Michal Trojanowski
  • 10,641
  • 2
  • 22
  • 41