0

Iam working on app, which consists from angular frontend and ASP net Web API backend(.net 4.5). For authentication iam using OpenIdConnect. I succesfully connected frontend to identity provider but now i need to validate id token on backend, so i can be sure, that only validated users can call backend.

This id token use rs256 algorithm for signing. So on backend, i need to do two things:

  1. Get JWKs from identity provider URL - iam a little lost here, should i get it throug normal HttpClient, or there is some library or helper function to do this?

  2. Generate RSA public key out of JWKs and validate token - for this iam using this function:

         string token="xyz..";
         RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    
         rsa.ImportParameters(
           new RSAParameters()
           {
               Modulus = FromBase64Url("xyz.."),
               Exponent = FromBase64Url("xyz..")
           });
    
         var validationParameters = new TokenValidationParameters
         {
             RequireExpirationTime = true,
             RequireSignedTokens = true,
             ValidateAudience = false,
             ValidateIssuer = false,
             ValidateLifetime = true,
             IssuerSigningKey = new RsaSecurityKey(rsa)
         };
    
         SecurityToken validatedSecurityToken = null;
         var handler = new JwtSecurityTokenHandler();
         handler.ValidateToken(tokenStr, validationParameters, out validatedSecurityToken);
         JwtSecurityToken validatedJwt = validatedSecurityToken as JwtSecurityToken;
    

It works, but now i need to connect it somehow with the loaded JWKs and register it to use this for every request that comes. Any advices or simple example would really help me. Thx.

Vartex05
  • 95
  • 8

1 Answers1

2

This code below is taken from one of my training classes and it will automatically download and validate the provided token and I hope you can use it as a reference. You typically will use the ConfigurationManager to download the IdentityServer configuration and JWKS for you. It will also internally cache and periodically (every 24) readload the config.

using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using OpenID_Connect_client.Models;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;

namespace OpenID_Connect_client
{
    public class TokenValidator
    {
        private readonly IOpenIDSettings openIDSettings;

        public TokenValidator(IOpenIDSettings openIDSettings)
        {
            this.openIDSettings = openIDSettings;
        }

        public string ValidateToken(string token, string clientId)
        {
            try
            {
                string issuer = openIDSettings.Issuer;

                var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{issuer}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
                var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result;

                // Configure the TokenValidationParameters. Assign the SigningKeys which were downloaded from Auth0. 
                // Also set the Issuer and Audience(s) to validate
                //https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
                var validationParameters =
                    new TokenValidationParameters
                    {
                        IssuerSigningKeys = openIdConfig.SigningKeys,

                        ValidAudiences = new[] { clientId },
                        ValidIssuer = issuer,
                        ValidateLifetime = true,
                        ValidateAudience = true,
                        ValidateIssuer = true,
                        ValidateIssuerSigningKey = true,
                        ValidateTokenReplay = true
                    };

                // Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method
                SecurityToken validatedToken;
                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                var user = handler.ValidateToken(token, validationParameters, out validatedToken);

                // The ValidateToken method above will return a ClaimsPrincipal. Get the user ID from the NameIdentifier claim
                // (The sub claim from the JWT will be translated to the NameIdentifier claim)
                return $"Token is validated. User Id {user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value}";
            }
            catch (Exception exc)
            {
                return "Invalid token: " + exc.Message;
            }
        }


    }
}
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • thx a lot, loading identity provider configuration works like a charm. Last thing i dont know is how to hook it up, so every request that comes to my backend is first validated with this function. Can i use something like app.UseJwtBearerAuthentication in Startup.cs, or i have to make it differently? – Vartex05 Jan 25 '21 at 19:32
  • If you add UseJwtBearerAuthentication, it will handle the token downloading and validation for you. Does this make this an acceptable answer? – Tore Nestenius Jan 26 '21 at 07:54
  • also, a good recommendation is to never put client or API in the same service as IdentityServer, better to keep them all separated, as it is much easier to reason about and troubleshoot when they are not in the same box. – Tore Nestenius Jan 26 '21 at 07:55
  • So, the core of this code is accurate, but returning a string is poor way to delineate between a valid and invalid result. In case it's helpful this same question is also answered here, with alternatives to help correctly formulate the Issuer for validation, etc.: https://stackoverflow.com/a/76478734/7293142 – CajunCoding Jun 15 '23 at 02:54