How to validate Azure AD security token?

82,964

Solution 1

There are two steps to verify the token. First, verify the signature of the token to ensure the token was issued by Azure Active Directory. Second, verify the claims in the token based on the business logic.

For example, we need to verify the iss and aud claim if you were developing a single tenant app. And you also need to verify the nbf to ensure the token is not expired. For more claims you can refer here.

Below description is from here about the detail of signature verifying. (Note: The example below uses the Azure AD v2 endpoint. You should use the endpoint that corresponds to the endpoint the client app is using.)

The access token from the Azure AD is a JSON Web Token(JWT) which is signed by Security Token Service in private key.

The JWT includes 3 parts: header, data, and signature. Technically, we can use the public key to validate the access token.

First step – retrieve and cache the signing tokens (public key)

Endpoint: https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

Then we can use the JwtSecurityTokenHandler to verify the token using the sample code below:

 public JwtSecurityToken Validate(string token)
 {
     string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";

     ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);

     OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;

     TokenValidationParameters validationParameters = new TokenValidationParameters
     {
         ValidateAudience = false,
         ValidateIssuer = false,
         IssuerSigningTokens = config.SigningTokens,
         ValidateLifetime = false
     };

     JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();

     SecurityToken jwt;

     var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);

     return jwt as JwtSecurityToken;
 }

And if you were using the OWIN components in your project, it is more easy to verify the token. We can use the code below to verify the token:

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
            });

Then we can use the code below to verify the ‘scope’ in the token:

public IEnumerable<TodoItem> Get()
{
    // user_impersonation is the default permission exposed by applications in AAD
    if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
    {
        throw new HttpResponseException(new HttpResponseMessage {
          StatusCode = HttpStatusCode.Unauthorized,
          ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found"
        });
    }
    ...
}

And here is a code sample which protected the web API with Azure AD:

Protect a Web API using Bearer tokens from Azure AD

Solution 2

Just wanted to add to Fei's answer for people using .net Core 2.0

You'll have to modify 2 lines of the Validate(string token) method.

 var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
        stsDiscoveryEndpoint,
        new OpenIdConnectConfigurationRetriever()); //1. need the 'new OpenIdConnect...'

 OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
 TokenValidationParameters validationParameters = new TokenValidationParameters
 {
     //decode the JWT to see what these values should be
     ValidAudience = "some audience",
     ValidIssuer = "some issuer",

     ValidateAudience = true,
     ValidateIssuer = true,
     IssuerSigningKeys = config.SigningKeys, //2. .NET Core equivalent is "IssuerSigningKeys" and "SigningKeys"
     ValidateLifetime = true
 };

Solution 3

But if you are not using OWIN in your projects, it is going to be a little hard or at least time consuming.. This articleHere is great resource.

And because I do not have much to add on the above, except the detailed code.. Here is something that can be useful to you:

 public async Task<ClaimsPrincipal> CreatePrincipleAsync()
    {
        AzureActiveDirectoryToken azureToken = Token.FromJsonString<AzureActiveDirectoryToken>();
        var allParts = azureToken.IdToken.Split(".");
        var header = allParts[0];
        var payload = allParts[1];
        var idToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryIdToken>();

        allParts = azureToken.AccessToken.Split(".");
        header = allParts[0];
        payload = allParts[1];
        var signature = allParts[2];
        var accessToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryAccessToken>();

        var accessTokenHeader = header.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureTokenHeader>();
        var isValid = await ValidateToken(accessTokenHeader.kid, header, payload, signature);
        if (!isValid)
        {
            throw new SecurityException("Token can not be validated");
        }
        var principal = await CreatePrincipalAsync(accessToken, idToken);
        return principal;
    }



    private async Task<bool> ValidateToken(string kid, string header, string payload, string signature)
    {
        string keysAsString = null;
        const string microsoftKeysUrl = "https://login.microsoftonline.com/common/discovery/keys";

        using (var client = new HttpClient())
        {
            keysAsString = await client.GetStringAsync(microsoftKeysUrl);
        }
        var azureKeys = keysAsString.FromJsonString<MicrosoftConfigurationKeys>();
        var signatureKeyIdentifier = azureKeys.Keys.FirstOrDefault(key => key.kid.Equals(kid));
        if (signatureKeyIdentifier.IsNotNull())
        {
            var signatureKey = signatureKeyIdentifier.x5c.First();
            var certificate = new X509Certificate2(signatureKey.ToBytesFromBase64URLString());
            var rsa = certificate.GetRSAPublicKey();
            var data = string.Format("{0}.{1}", header, payload).ToBytes();

            var isValidSignature = rsa.VerifyData(data, signature.ToBytesFromBase64URLString(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            return isValidSignature;
        }

        return false;
    }

There are some functions that I use in here that are not available for you, they are self descriptive.

Share:
82,964
Neo
Author by

Neo

Hi! I'm a software engineer. Having experience to work on C#,Asp.Net,AJAX,SQL 2005/08/2012,EF4,SQL to LINQ and also worked on Cloud Computing (Microsoft Windows Azure and AWS Amazon).

Updated on January 17, 2022

Comments

  • Neo
    Neo over 2 years

    The following code gives me Azure AD security token, I need to validate that token is valid or not. How to achieve this?

    // Get OAuth token using client credentials 
    string tenantName = "mytest.onmicrosoft.com";
    string authString = "https://login.microsoftonline.com/" + tenantName;
    
    AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
    
    // Config for OAuth client credentials  
    string clientId = "fffff33-6666-4888-a4tt-fbttt44444";
    string key = "123v47o=";
    ClientCredential clientCred = new ClientCredential(clientId, key);
    string resource = "http://mytest.westus.cloudapp.azure.com";
    string token;
    
    Task<AuthenticationResult> authenticationResult = authenticationContext.AcquireTokenAsync(resource, clientCred);
    token = authenticationResult.Result.AccessToken;
    Console.WriteLine(token);
    // How can I validate this token inside my service?                
    
  • Quarkly
    Quarkly about 7 years
    I'm a little confused about the validation of the token. Let's say I'm running an asset exchange. I've got a user who obtained a security token at 12 PM. At 1 PM I discover that they're violating the exchange rules and I go into the AAD portal and block that user's sign-ins. Do I understand that it will take a day or two before that token will no longer work? Is there anything more immediate that will disable this user?
  • Fei Xue - MSFT
    Fei Xue - MSFT about 7 years
    At present, it is not able to revoke the access token already issued by Azure AD. However, we can disable the users sign-in for the app immaculately by enable the User assignment required to access app feature on the Azure portal and disable the user. If you want Azure AD to support revoking the access token, you can submit the feedback from here.
  • Steven Liekens
    Steven Liekens almost 6 years
    Please don't post answers that disable the three most important validation rules. Also you should probably not use that in production yourself.
  • Ambrose Leung
    Ambrose Leung almost 6 years
    good point, i changed it from 'false' to 'true' - I was just going off of the original answer
  • Steven Liekens
    Steven Liekens almost 6 years
    @FeiXue please don't turn off important validation rules in your sample code (ValidateAudience, ValidateIssuer, ValidateLifetime) before somebody copy-pastes your sample and thinks it's good to go for production because it works.
  • krlzlx
    krlzlx over 5 years
    Where do you put your Validate method and where do you call it?
  • JustAMartin
    JustAMartin over 4 years
    I"m curious - who and how is managing the caching of the public key and in which cases the cache gets updated?
  • Sergio Solorzano
    Sergio Solorzano about 4 years
    Hi @StevenLiekens can you shed some light how it'd be in production or a link to get further insight?
  • Ambrose Leung
    Ambrose Leung about 4 years
    @SergioSolorzano I had ValidateXXX = false initially (because I copied and pasted it from the marked answer), I've already corrected it to ValidateXXX = true which is what Steven was referring to (that it should be set to 'true' in production)
  • Bronek
    Bronek almost 4 years
    But how to configure config.SigningTokens? Azure AD gives access to keys through dedicated Uri.
  • Bronek
    Bronek almost 4 years
    But how to configure config.SigningTokens? Azure AD gives access to keys through dedicated Uri
  • s k
    s k almost 4 years
    I am using MS OAuth 2 to sign to my web. I can't find my key in microsoftKeysUrl. Is there anywhere else that I can obtain the correct key?
  • Assil
    Assil almost 4 years
    Are you saying that you know the keys id or name in the token but you are not able to find it login.microsoftonline.com/common/discovery/keys ??? That is strange, It should be found here. The problem however is that they have changed the version of signing algorithm and introduced the nonce which will make it hard for you to verify it. Which endpoint are you hitting to get the token V1 or V2 ?
  • s k
    s k almost 4 years
    After trying for 1 whole day, I managed to find my kid here. login.microsoftonline.com/common/discovery/v2.0/keys. But then I have to enable the implicit options inside AD Otherwise the key won't show.
  • Assil
    Assil almost 4 years
    Nice to hear.. So yea, I see you specified the version to get the key. Things are changing fast.
  • Patrick Knott
    Patrick Knott almost 4 years
    This is the way it works in .net Framework 4.7.2 now as well. Azure gives keys not signing tokens.
  • Patrick Knott
    Patrick Knott almost 4 years
    See Ambrose Leung's answer below. This is currently out of date. SigningTokens are no longer used by Azure.
  • FritsJ
    FritsJ over 3 years
    Where can I get/generate my own SigningKeys?
  • jklemmack
    jklemmack over 3 years
    @FritsJ Use OpenSSL to generate your own keys. Sweet and to the point example is at stackoverflow.com/a/10176685/508681
  • Tomas Jansson
    Tomas Jansson about 3 years
    Trying to use this but I get a "Microsoft.IdentityModel.Tokens.SecurityTokenException: IDX10612: Decryption failed. Header.Enc is null or empty, it must be specified." Any idea how to deal with that?
  • Zimano
    Zimano about 3 years
    @Patrick Indeed, it's been changed to SigningKeys, also see stackoverflow.com/questions/45500752/…