1

Once we get the JWT token in the frontent, we can pass is using Authorization header or through cookies for authenticating our stateless RestAPIs in the backend server. Nicely explained in this video.

If the backend server is in C# .Net Framework (MVC), how can the received JWT be validated? The official documentation points towards OWIN, which is not maintained.

When seeing the various blogs and documentation, the theory says we need to get the Modulus & Exponent parameters from the Keycloak Realms' public certificate, and then verify it using JWT.Net

How can that be achieved?

  • Take a look at this https://stackoverflow.com/questions/40281050/jwt-authentication-for-asp-net-web-api – feihoa Jun 16 '21 at 11:24

2 Answers2

0

Get the public certificate of your realm through keycloak:

Getting public certificate

You will get something like this that needs to be formatted properly:

Raw Certificate Data

  1. Copy the PublicCertificate of your realm

  2. Save it in a KeyCloakRealm.Public.crt file

  3. Add header -----BEGIN CERTIFICATE-----

  4. Make the single line certificate to be in 64bytes in each line

  5. Add footer -----END CERTIFICATE-----

Crt file format

Then the following code snippet might be used to validate the received JWT token.

X509Certificate2 certificate = new 
X509Certificate2("KeyCloakRealm.Public.crt");

RSACryptoServiceProvider key =(RSACryptoServiceProvider)certificate.PublicKey.Key;
RSAParameters rsaParameters = key.ExportParameters(false);

RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);

var json = JwtBuilder.Create()
         .WithAlgorithm(new RS256Algorithm(rsa)) // asymmetric
         .MustVerifySignature()
         .Decode(token);
// The above method will throw an appropriate error if the JWT is invalid or cannot be validated against the supplied public keycloak realm
// If there is no exception, you will get the data in your json object
  • @MickyD: The image and the above steps tell how to properly format the certificate that is received through keycloak. Until formatted, the C# code won;t be able to recognize the format. Unix might be able to deal with it though. This is never explained in the documetation. This will help candidates using Keycloak on C# save much time. – Anshuman Chatterjee Jun 16 '21 at 11:27
  • 1
    This is not working "InvalidCastException: Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'." – GreenEyedAndy Oct 28 '22 at 12:19
0

OAuth specs access to the keys via 'http://myAuth.com/.well-known/jwks.json'. So you can just use the built in JwtSecurityTokenHandler to validate the token without having to configure the certificate in your application. This is also superior because it accommodates key cycling without redeploying your services.

OWIN Example:

public static class AppBuilderExtensions
{
    /// <summary>
    /// Use JWT Authentication.
    /// </summary>
    /// <param name="app">The <see cref="IAppBuilder"/> to use.</param>
    /// <param name="audiences">The expected audiences.  Will be validated in the token.</param>
    /// <returns>The <see cref="IAppBuilder"/> instance.</returns>
    public static IAppBuilder UseJwtTokenAuthentication(
        this IAppBuilder app,
        params string[] audiences)
    {
        var validationParameters = new TokenValidationParameters
                                   {
                                       ValidateLifetime = true,
                                       ValidateAudience = true,
                                       ValidateIssuer = true,
                                       ValidateActor = true,
                                       ValidateIssuerSigningKey = true,
                                       ValidAudiences = audiences,
                                       ValidIssuer = Constants.Issuer,
                                       IssuerSigningKeyResolver = Constants.GetSigningKey,
                                   };

        var tokenHandler = new JwtSecurityTokenHandler();
        app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = audiences,
                TokenHandler = tokenHandler,
                TokenValidationParameters = validationParameters,
            });

        return app;
    }
}

public static class Constants
{
    /// <summary>
    /// The authentication issuer.
    /// </summary>
    public const string Issuer = "https://myAuth.com/";

    private static readonly OpenIdConnectKeyResolver KeyResolver;

    static Constants() { KeyResolver = new OpenIdConnectKeyResolver(Issuer, TimeSpan.FromHours(1), TimeSpan.FromSeconds(10)); }

    /// <summary>
    /// Gets the <see cref="IssuerSigningKeyResolver" /> delegate to provide to <see cref="JwtSecurityTokenHandler" />.
    /// </summary>
    public static IssuerSigningKeyResolver GetSigningKey => KeyResolver.GetSigningKey;
}

Direct call Example:

        void ValidateToken(string authenticationToken)
        {
            var issuer = "https://myAuth.com/";
            var audiences = new[]
                            {
                                "https://myApplication.com/",
                            };

            using (var signingKeyResolver = new OpenIdConnectKeyResolver(issuer, TimeSpan.Zero, TimeSpan.FromSeconds(10)))
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var validationParameters = new TokenValidationParameters
                                           {
                                               ValidateLifetime = true,
                                               ValidateAudience = true,
                                               ValidateIssuer = true,
                                               ValidateActor = true,
                                               ValidateIssuerSigningKey = true,
                                               ValidAudiences = audiences,
                                               ValidIssuer = issuer,
                                               IssuerSigningKeyResolver = signingKeyResolver.GetSigningKey,
                                           };

                var principal = tokenHandler.ValidateToken(authenticationToken, validationParameters, out var securityToken);

                if (principal == null || securityToken == null)
                {
                    throw new UnauthorizedAccessException();
                }
            }
        }

This is baked into Microsoft.IdentityModel.Tokens (see example here: https://github.com/auth0/auth0-aspnet-owin/blob/master/src/Auth0.Owin.OpenIdConnectSigningKeyResolver/OpenIdConnectSigningKeyResolver.cs).

It is a little more work if using System.IdentityModel.Tokens.Jwt (see example here: https://github.com/NaosProject/Naos.Auth/blob/main/Naos.Auth.Recipes.Jwt/OpenIdConnectKeyResolver.cs) I wrote a piece of code to accomplish this which can be copied directly (MIT license) or installed as a mix-in using package 'Naos.Auth.Recipes.Jwt'.

wlscaudill
  • 505
  • 4
  • 6