Azure Active Directory simplifies the burden of Authentication and Authorization process while securing an application.

But have you ever guessed, how could the process flow is so robust and flawless, so that the token (JWT token) is verified for the issuer and cannot be tampered.

Let’s have a look at how we can secure a simple .Net minimal api using the AAD Authentication (Microsoft Identity Platform).

So, just jump in to create a minimal api using Visual Studio.

Prerequisites
Visual Studio 2022
Dotnet 7 Runtime

Create an api project project using Visual Studio



image


We need only minimal setup to implement Microsoft.Platform authentication.



image

The solution looks simple as:




Right click the connected services to add the Microsoft Identity platform as a connected service dependency.



Click plus button.



image

Search identity



image

Click Next.



image

Click



It lists the App Registrations in the Active Directory.

We are using a new app registration to authenticate our app.

So let’s click on the create button.



image

Name the app and tap Register



image

Tap Next

Summary would look like



image

If the App Registration is completed successfully, you would see a screen similar to this.



image

Now that, you have added the Microsoft Identity Platform dependency successfully, you can see some additional codes in your Program.cs. There would be some additional packages installed and referenced in the Program.cs.



        using Microsoft.AspNetCore.Authentication;
        using Microsoft.AspNetCore.Authentication.JwtBearer;
        using Microsoft.Graph;
        using Microsoft.Identity.Web;

and these lines of codes will implement the Microsoft Identity authentication using AAD.

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

In applicationsettings.json you can find the AzureAd section updated as per the details of the App Reg we have created MinimalAuthApp.


     "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "mydomain.com",
        "TenantId": "xxxxxx-xxxxxx-xxxxxx-xxxxxx-xxxxxx",
        "ClientId": "xxxxxx-xxxxxx-xxxxxx-xxxxxx-xxxxxx",
        "CallbackPath": "/signin-oidc",
        "Scopes": "access_as_user"
     }

Lets keep it simple; open Program.cs and remove the swagger configuration.


// Remove
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Update launchSettings.json

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:6850",
      "sslPort": 44315
    }
  },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "weatherforecast", //👈 change url
      "applicationUrl": "http://localhost:5086",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "weatherforecast", //👈 change url
      "applicationUrl": "https://localhost:7116;http://localhost:5086",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "weatherforecast", //👈 change url
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Start you application, this will launch the application on /weatherforecast endpoint.

We didn’t add any Authorization header, so, as expected 401 page.

image

Where we can get the token fromm????

If you check the microsoft documentation, it describes different flows and request to get a token from the end points, ie, the AAD endpoints.

For simplicity we are using implicit flow and the url to generate a token in the callback looks like this…

https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize?
client_id={client_id}
&response_type=token
&redirect_uri=https%3A%2F%2Flocalhost%3A7116%2Fmyapp%2F
&scope=api%3A%2F%2F0ec35133-b038-49eb-86d0-fb1c37fd17bd%2Faccess_as_user
&response_mode=fragment
&state=12345
&nonce=678910
&prompt=none


Don’t forget to configure the redirect url and token flow (here implicit) and scope in your App Registration and make a call to the token end point.

You will get the callback url and token as a fragment:

https://localhost:7116/myapp/#access_token=eyJ0eXAiOiJKV1QiLCJhbGciOixxxxxxx.eyJhdWQiOiIwZWMzNTEzMy1iMDM4LTQ5xxxxxxx.TmRzKkvl7TemO9y64nXHHkKjlhmJXM_Hx240QpMVxxxxxxx&token_type=Bearer&expires_in=4928&scope=api%3a%2f%xxxxxxxxxxxxxxxxxx%2faccess_as_user&state=12345&session_state=xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Copy the token and use postman to make a post call with the token as the Bearer Authorization header.

image Yay….. Go got a response authenticated by Azure AAD.

So far so good….

But now comes the difficult question….

How does your application knows that the token it received is actually generated by the AAD itself. Or, why I could not make a JWT token and authenticate the application.

In order to cut the clutter, let’s have look at the roken first.

Go to the jwt.io and decrypt the token received.

HEADER:ALGORITHM & TOKEN TYPE

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "-KI3Q9nNR7bxxxxxxxxxxx"
}
PAYLOAD:DATA

{
  "aud": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "iss": "https://login.microsoftonline.com/{tenant_id}/v2.0",
  "iat": 1680620723,
  "nbf": 1680620723,
  "exp": 1680625952,
  "aio": "AZQAaaaaaa/8TAAxxxxxxxxxxxxxxxxxxxxx/40bZJVqxIY1Hsn+VuQJOFxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "azp": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "azpacr": "0",
  "name": "Arun Paul (Polly)",
  "oid": "xxxxxxxxxxxxxxxxxxxxx",
  "preferred_username": "apaul@ariqt.com",
  "rh": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "scp": "access_as_user",
  "sub": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "tid": "8xxxxxxxxxxxxxxxxxxxxx",
  "uti": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "ver": "2.0"
}
VERIFY SIGNATURE

RSASHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  
{
  "e": "AQABADAD",
  "kty": "RSA",
  "n": "btsbnnrsn6wSoDAhgRYSDkaMuEHy75VikiB8wg25WuR96gdMpookdlRvh7SnRvtjQN9b5m4zJCMpSRcJ5DuXl4mcd7Cg3Zp1C5-JmMq8J7m7OS9HpUQbA1yhtCHqP7XA4UnQI28J-TnGiAa3viPLlq0663Cq6hQw7jYo5yNjdJcV5-FS-xNV7UHR4zAMRruMUHxte1IZJzbJmxjKoEjJwDTtcd6DkI3yrkmYt8GdQmu0YBHTJSZiz-M10CY3LbvLzf-tbBNKQ_gfnGGKF7MvRCmPA_YF_APynrIG7p4vPDRXhpaggtehbths"
}
,
  
Private Key in PKCS #8, PKCS #1, or JWK string format. The key never leaves your browser.

)

The payload has the data we expected from the AAD App registration. And interestingly the token came signature verified.

image

The AAD uses a private key to sign the token and the only the public key pair can decrypt it. So the token came signature verified with the public key.

If you want to know more about generating a JWT token with key pairs, please read my blog here.

Still the question is not completely answered….

How does the application verify that the public key used for successfully decrypt the token was generated by AAD itself??

Your application became smarter with the Microsoft.Identity platform to verify this.

To really understand what does it happen under the hood when you try to authenticate/authorize your application, we need some http interceptor to analyze all the http calls the application makes.

Fiddler Everywhere is a great tool for that, which can be installed free on a trial basis.

Once you installed the tool, open it

Clear the Fiddler pane for cleaner analysis, you can toggle the live streaming to off position to remove the clutter of calls,except for the application calls.

Now go to the postman and make a call to `` with the Authentication token. Lets see what all http calls we could intercept during the process.

We can see that there are two call our application makes to the AAD endpoints.

image

Lets have a closer look…

image

First call is made to the endpoint https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration

GET https://login.microsoftonline.com/{tenant_id}/v2.0/.well-known/openid-configuration HTTP/1.1
Host: login.microsoftonline.com
x-client-Ver: 6.17.0.0
x-client-SKU: ID_NETSTANDARD2_0
User-Agent: Microsoft ASP.NET Core JwtBearer handler
....................


where the repose gives another AAD endpoint for the public keys.

{
    "token_endpoint": "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
    "token_endpoint_auth_methods_supported": [
        "client_secret_post",
        "private_key_jwt",
        "client_secret_basic"
    ],
    "jwks_uri": "https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys",
    "response_modes_supported": [
        "query",
        "fragment",
        "form_post"
    ],
   .........
   .........
}

The second call is to the exact same public key endpoint received in the first call "jwks_uri". ie, ``

GET https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys HTTP/1.1
Host: login.microsoftonline.com
x-client-Ver: 6.17.0.0
x-client-SKU: ID_NETSTANDARD2_0
User-Agent: Microsoft ASP.NET Core JwtBearer handler
...................
....................



And the response is a list of public keys…

{
    "keys": [
        {...
         },
         {...
         },
         {...
         },
         {...
         },
         {...
         },
        {
            "kty": "RSA",
            "use": "sig",
            "kid": "-KI3Q9nNR7bxxxxxxxxxxx",
            "x5t": "-KI3Q9nNR7bxxxxxxxxxxx",
            "n": "tJL6Wr2JUsxLyNezPQh1J6z.........................
            ........................
            .............................",
            "e": "AQABD",
            "x5c": [
                "MIIDBTCCAe2gAwIBAg.....................................
                ........................................................
                ......................................."
            ],
            "issuer": "https://login.microsoftonline.com/{tenant_id}/v2.0"
        },       
        {...
         },
        {...
        }
    ]
}

If we inspect we could see that one of the keys, is matching with the key id kid in the header of the token we used.

HEADER:ALGORITHM & TOKEN TYPE

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "-KI3Q9nNR7bxxxxxxxxxxx"
}

And using this public key the token is already signature verified….

Yeppie…we got the answer (infact, the application got the answer), the token is verified by one of the public keys issued by the AAD.

Yes, lets go ahead and trust the token, its from the safe hands….

Happy coding….