简体   繁体   中英

Manually validating a JWT token in C#

I am having some trouble manually validating a JWT token issued by Identity Server 4. Using the

ClientId: "CLIENT1" ClientSecret: "123456"

The exception I keep getting is: IDX10501: Signature validation failed. Unable to match keys: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'

Is anyone able to advise me where I am going wrong.

    private static void ValidateJwt(string jwt, DiscoveryResponse disco)
    {        

        var parameters = new TokenValidationParameters
        {

            ValidateIssuerSigningKey = true,
            ValidIssuer = disco.Issuer,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456")),               
            ValidAudience = "CLIENT1",
            //IssuerSigningKeys = keys,
            // ValidateAudience = true,
            // ValidateLifetime = true,
        };

        SecurityToken validatedToken;
        var handler = new JwtSecurityTokenHandler();
        handler.InboundClaimTypeMap.Clear();

        try
        {
            var user = handler.ValidateToken(jwt, parameters, out validatedToken);
        }
        catch(Exception ex)
        {
            var error = ex.Message;
        }

    }

Check out ValidateJwt() in this sample:

https://github.com/IdentityServer/IdentityServer4/blob/master/samples/Clients/old/MvcManual/Controllers/HomeController.cs

The bit you're missing is loading the public key from the discovery document.

Try changing the length of your private key. Your private key is too small to be encoded I suppose.

For manual verification, you could just use

static byte[] FromBase64Url(string base64Url)
        {
            string padded = base64Url.Length % 4 == 0
                ? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
            string base64 = padded.Replace("_", "/")
                                  .Replace("-", "+");
            return Convert.FromBase64String(base64);
        }

This also answers @henk-holterman 's question

Encoding.UTF8.GetBytes( can't be the right way to do this.

Although realistically a better way to do this is via the OIDC discovery-endpoint Auth0 has a good article about this using standard NuGet packages. Basically, you load everything needed from the discovery end-point.

IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
TokenValidationParameters validationParameters =
    new TokenValidationParameters
    {
        ValidIssuer = auth0Domain,
        ValidAudiences = new[] { auth0Audience },
        IssuerSigningKeys = openIdConfig.SigningKeys
    };
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken("eyJhbGciOi.....", validationParameters, out validatedToken);

You can read more about it here Or their GitHub sample page about this here

In my case, I did not have a discovery endpoint. Just a JWKS endpoint. So I opted to do this.

using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;

public class ExpectedJwksResponse
    {
        [JsonProperty(PropertyName = "keys")]
        public List<JsonWebKey> Keys { get; set; }
    }

private static async Task<List<SecurityKey>> GetSecurityKeysAsync()
        {
            // Feel free to use HttpClient or whatever you want to call the endpoint.
            var client = new RestClient("<https://sample-jwks-endpoint.url>");
            var request = new RestRequest(Method.GET);
            var result = await client.ExecuteTaskAsync<ExpectedJwksResponse>(request);
            if (result.StatusCode != System.Net.HttpStatusCode.OK)
            {
                throw new Exception("Wasnt 200 status code");
            }

            if (result.Data == null || result.Data.Keys == null || result.Data.Keys.Count == 0 )
            {
                throw new Exception("Couldnt parse any keys");
            }

            var keys = new List<SecurityKey>();
            foreach ( var key in result.Data.Keys )
            {
                keys.Add(key);
            }
            return keys;
        }

private async Task<bool> ValidateToken(token){
    TokenValidationParameters validationParameters = new TokenValidationParameters
            {
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                ValidateLifetime = true,
                ValidIssuer = "https://sample-issuer.com",
                ValidAudiences = new[] { "https://sample-audience/resource" },
                IssuerSigningKeys = await GetSecurityKeysAsync()
            };
    var user = null as System.Security.Claims.ClaimsPrincipal;
    SecurityToken validatedToken;
    try
    {
        user = handler.ValidateToken(token, validationParameters, out validatedToken);
    }
    catch ( Exception e )
    {
        Console.Write($"ErrorMessage: {e.Message}");
        return false;
    }
    var readToken = handler.ReadJwtToken(token);
    var claims = readToken.Claims;
    return true;
}

You have specified:

IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secret"))

but the JwtSecurityTokenHandler could not match it with the key which can be part of jwt header itself . Basically it means that your configuration has mismatch[es] with configuration of the real issuer. The error suggests that this relates to the signature keys.

Please, check the configuration of that issuer (if you can), find out missed parts, and try again.

You can use jwt.io to debug your jwt online.

IdentityServer signs the JWT using RS256. This means you need to use a public key to verify the JWT (you can get this from the discovery document).

The client id & client secret are client credentials used for requesting tokens. They have no part in validating them.

You are trying to use SymmetricKey for JWT validation. Try looking your token in JWT.io and if algorithm is"RS256" then SymmetricKey won't work.

Please check when you create JWT token make sure that you added SigningCredentials.

var token = new JwtSecurityToken(
  Constants.Audiance,Constants.Issuer,claims
   notBefore:DateTime.Now,
   expires:DateTime.Now.AddHours(1),
   **signinCredential**
   );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM