4

Scenario:

I have an angular5 client application, which uses hello.js to authenticate users using their office 365 credentials.

Client Code:

  hello.init({
      msft: {
        id: configuration.AppID,
        oauth: {
          version: 2,
          auth: 'https://login.microsoftonline.com/' + configuration.TenantID + '/oauth2/v2.0/authorize'
        },
        scope_delim: ' ',
        form: false
      },
    },
      { redirect_uri: configuration.redirecturl }
    );
  }


  login() {

    hello('msft').login({ scope: 'User.Read People.Read', display: 'popup' })
      .then((authData: any) => {  // console.log(authData);

        this.zone.run(() => {

          // get profile
}

A successful response is (Manipulated for security reasons)

{  
   "msft":{  
      "access_token":"REMOVED TOKEN HERE",
      "token_type":"Bearer",
      "expires_in":3599,
      "scope":"basic,User.Read,People.Read",
      "state":"",
      "session_state":"3b82898a-2b3f-445363f-89ae-d9696gg64ad3",
      "client_id":"672330148-2bb43-3080-9eee-1f46311f789c",
      "network":"msft",
      "display":"popup",
      "redirect_uri":"http://localhost:5653/",
      "expires":15245366.218
   }
}

The decoded access_token has these few keys:

Header:

1. nonce (requires some special processing, I couldn't find any documentation regarding special processing)

2. typ: JWT

PayLoad:

"aud": "https://graph.microsoft.com",

Once the access_token is received, I am sending the access_token in authorization header of every call to my backend API. The goal is to validate the token and only send a successful response if the access_token is validated and authorized. If unsuccessful, 401 Unauthorized is the response.

API Code to validate access_token, ASP .NET CORE 2, Following (https://auth0.com/blog/securing-asp-dot-net-core-2-applications-with-jwts/)

namespace JWT
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
          options.TokenValidationParameters = new TokenValidationParameters
          {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Issuer"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
          };
        });

      services.AddMvc();
    }
  }
}

// other methods
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAuthentication();

    app.UseMvc();
}

In appsettings.json I have:

{   "Jwt": {
    "Key": "verySecretKey", **(I got the key from https://login.microsoftonline.com/common/discovery/keys with the kid value in access_token header)**
    "Issuer": "https://sts.windows.net/49bcf059-afa8-4bf9-8470-fad0c9cce27d/",   } }

Finally, the error I receive is : "WWW-Authenticate →Bearer error="invalid_token", error_description="The signature key was not found""

I have been stuck here since past few days, any help will be life savior.

Key Points:

  1. I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.

  2. The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?

  3. Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.

Please let me know if you need more information.

3 Answers 3

2

I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.

Microsoft Graph API access tokens are signed differently from other access tokens from what I can see. You do not need to validate tokens that are meant for another API, it is their job.

The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?

I don't know about HelloJS, but you should be able to get an Id token after authentication with response_type=id_token token. Then you need to attach that to the requests. It should have your client id as the audience.

Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.

The only thing that stands out to me is that you are doing a lot of unnecessary configuration. Basically the configuration should be:

.AddJwtBearer(o =>
{
    o.Audience = "your-client-id";
    o.Authority = "https://login.microsoftonline.com/your-tenant-id/v2.0";
})

The handler will automatically fetch the public signing keys on startup. It's not really a good idea to hard-code signing keys in your app since your app will break when AAD finishes signing key rollover.

Sign up to request clarification or add additional context in comments.

10 Comments

Hey @juunas, thank you for your response. so if Microsoft GRAPH API access tokens are meant to be validated by them, which flow should I follow to secure my backend API. I followed the authorization code grant protocol and I got an Id_token where the audience is my client_id but it has no information about the user signed in..Without user information, how do I validate the token & authorize the user?
Your backend API should not use authorization codes, it should use JWT Bearer tokens. You can get the user information from the access token sent to your API.
Yes, I am not using authorization code, I use the code to get the access token and ID token. (learn.microsoft.com/en-us/azure/active-directory/develop/…). So do you mean I have to send two tokens (id_token and access token) to my backend API and validate ID_token and decode access token to get the user information?
The id token is technically not meant for your backend :) If you decode you can see it has a different audience (your frontend).
I am so confused, do you have any demo/tutorial that I can follow? Appreciate your help
|
1

I also spent a lot of time trying to validate it, but the bottom line is that you can't:

Access tokens are opaque blobs of text that are for the resource only. If you're a client getting a token for Graph, assume that it's an encrypted string that you should never look at - sometimes it will be. We use a special token format for Graph that they know how to validate - you shouldn't be looking at access tokens if they're not for you. (source: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609)

Instead of using the access token, you should create an ID token, which is a regular JWT token that can be validated like any other JWT:

  1. Get the public key from the Microsoft directory
  2. Validate the signature, audience, issuer, etc.

To get an ID token using the MSAL API after login you can do (javascript example):

const { instance, accounts } = useMsal();
const request = {
    scopes: ["User.Read"],
    account: accounts[0]
};
const idToken = await instance.acquireTokenSilent(request).idToken;

For more information on ID Tokens, please check:

https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens

For more information on opaque tokens, please check:

https://zitadel.com/blog/jwt-vs-opaque-tokens

Comments

-1

Yeah, this took a bit to work through. For anyone else researching this, here's my understanding.

You don't use the Microsoft Graph API to secure your web api. Instead:

  1. The client continues to use the Microsoft Identity Platform to authenticate.

  2. The client uses the resulting JWT access token to call the Web API as normal for OAuth 2.0 flow

  3. The web API uses JwtBearerAuthenticationScheme, setting the authority to the Microsoft identity platform. See this example and search for JwtBearerAuthenticationScheme.

  4. The web API uses the provided access token to obtain an 'On Behalf Of' user token.

  5. The web API calls the Graph API using this 'On Behalf Of' token. This token has a different lifespan than the token the client obtained, and refreshes must be handled separately.

This is a very distilled version of this example. Disclaimer: I haven't put this into practice yet.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.