Custom token validator not working in .NET 8
Asked Answered
L

2

6

Today I was checking if I can migrate my app to .NET 8. At first everything was ok by when I tried to log in, I get following error in the console:

Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10506: Signature validation failed. The user defined 'Delegate' specified on TokenValidationParameters did not return a 'Microsoft.IdentityModel.JsonWebTokens.JsonWebToken', but returned a 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' when validating token

My client is using customized implementation of PingID. Tokens are validated using introspection that why I've written custom token validator:

public class PingTokenValidator : ISecurityTokenValidator
{
    private readonly IConfiguration configuration;
    private readonly ILogger log;
    private readonly OpenIdConnectConfiguration openIdConnectConfiguration;
    private readonly JwtSecurityTokenHandler tokenHandler;

    public PingTokenValidator(OpenIdConnectConfiguration openIdConnectConfiguration,
        IConfiguration configuration)
    {
        this.tokenHandler = new();
        this.openIdConnectConfiguration = openIdConnectConfiguration;
        this.configuration = configuration;
        this.log = Log.ForContext<PingTokenValidator>();
    }

    public bool CanReadToken(string securityToken)
    {
        return true;
    }

    public ClaimsPrincipal ValidateToken(string securityToken,
        TokenValidationParameters validationParameters,
        out SecurityToken validatedToken)
    {
        try
        {
            var principal = this.tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);

            if (tokenExistInCache && cachedToken == securityToken)
            {
                return principal;
            }

            if (!this.IsValid(securityToken))
            {
                throw new SecurityTokenValidationException("Token not authorised by PingID.");
            }

            return principal;
        }
        catch (Exception e)
        {
            this.log.Error(e, "Error validating JWT token");
            throw;
        }
    }

    public bool CanValidateToken { get; }

    public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

    private bool IsValid(string securityToken)
    {
        var options = new RestClientOptions
        {
            Authenticator = new HttpBasicAuthenticator(this.configuration["FDID:UserId"]!, this.configuration["FDID:Secret"]!)
        };
        var client = new RestClient(options);
        var request = new RestRequest(this.openIdConnectConfiguration.IntrospectionEndpoint, Method.Post);
        request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
        request.AddParameter("grant_type", "urn:pingidentity.com:oauth2:grant_type:validate_bearer");
        request.AddParameter("token", securityToken);
        var response = client.Execute(request);

        if (!response.IsSuccessful)
        {
            this.log.Error(response.ErrorException, "Error validating JWT token. Response message: {@TokenIntrospectionMessage}", response.Content);
            return false;
        }

        if (string.IsNullOrWhiteSpace(response.Content))
        {
            this.log.Error("Ping returned empty response: {@IntrospectionResponse}", response);
            return false;
        }

        var tokenSummary = JsonSerializer.Deserialize<PingTokenSummary>(response.Content, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });

        return tokenSummary is
        {
            Active: true
        };
    }
}

I was looking for other interfaces that could be used instead of ISecurityTokenValidator but with no luck. Am I missing something?

Ligialignaloes answered 20/11, 2023 at 10:49 Comment(2)
Make sure JWT is valid and using the correct JsonWebKey. Have you manually validate the signature in jwt.io ? Did it show "signature verified" ?Hailstone
Try locating your authenticationBuilder.AddJwtBearer(options => and make sure that SignatureValidator = delegate returns instance of new JsonWebToken and not new JwtSecurityToken as was my case. It fixed it for me.Wept
W
12

It seems that Microsoft has now stricter requirements about what excact type can be returned from TokenValidationParameters.SignatureValidator. You are setting that in method like this

            authenticationBuilder.AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = false,
                        ValidateIssuerSigningKey = false,
                        SignatureValidator = delegate (string token, TokenValidationParameters parameters)
                        {
                            var jwt = new JsonWebToken(token); // here was JwtSecurityToken
                            if (parameters.ValidateIssuer && parameters.ValidIssuer != jwt.Issuer)
                                return null;
                            return jwt;
                        },
                        ValidIssuer = configuration.JwtIssuer,
                        ValidAudience = configuration.JwtAudience,
                        RequireSignedTokens = false
                    };
#if DEBUG
                    options.IncludeErrorDetails = true;
#endif
                }
            );

My old code in the SignatureValidator delegate was returning instance of JwtSecurityToken and when I changed it to 'JsonWebToken' it started working. Notice that each is also from different namespace as the error message correctly suggests.

Hope it works even for you.

Wept answered 24/11, 2023 at 14:22 Comment(1)
Thank you! Upvoted. This breaking change is documented here: learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/…Middy
Q
1

According to Microsoft docs in here the easiest way to fix this is to set

UseSecurityTokenValidators = true;

when adding JWTBearer like this :

AddJwtBearer(jwt =>
    {
        jwt.SaveToken = true;
        jwt.TokenValidationParameters = tokenValidationParams;
        jwt.UseSecurityTokenValidators = true;
        jwt.SecurityTokenValidators.Clear();
        jwt.SecurityTokenValidators.Add(services.BuildServiceProvider().GetRequiredService<ISecurityTokenValidator>());
    });
Quietus answered 27/11, 2023 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.