Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
        }

    }
like image 534
MarzSocks Avatar asked Feb 16 '18 08:02

MarzSocks


People also ask

How do you verify JWT token?

To verify JWT claimsVerify that the token is not expired. The aud claim in an ID token and the client_id claim in an access token should match the app client ID that was created in the Amazon Cognito user pool. The issuer ( iss ) claim should match your user pool.

How do you validate a token?

There are two ways to verify a token: locally or remotely with Okta. The token is signed with a JSON Web Key (JWK) using the RS256 algorithm. To validate the signature, Okta provides your application with a public key that can be used.

How do I validate a JWT token in Web API?

The workflow is to use the JWT library (NuGet package above) to validate the JWT token and then return back ClaimsPrincipal . You can perform more validation, like check whether user exists on your system, and add other custom validations if you want.


3 Answers

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.

like image 158
mackie Avatar answered Oct 19 '22 18:10

mackie


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

like image 41
Arjit Sharma Avatar answered Oct 19 '22 20:10

Arjit Sharma


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;
}
like image 2
The 0bserver Avatar answered Oct 19 '22 20:10

The 0bserver