14

I am getting invalid signature while using jwt.io to validate my azure ad access token. My id token, however, validates just fine!

I have seen and tried the solutions suggested in
Invalid signature while validating Azure ad access token
and
https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx
but neither works for my access token.

The access and Id token is generated via Adal.js:

    var endpoints = {
        "https://graph.windows.net": "https://graph.windows.net"
    };
    var configOptions = {
        tenant: "<ad>.onmicrosoft.com", // Optional by default, it sends common
        clientId: "<app ID from azure portal>",
        postLogoutRedirectUri: window.location.origin,
        endpoints: endpoints,
    }
    window.authContext = new AuthenticationContext(configOptions);

Why can I validate my ID token, but not my access token?

Jeppe
  • 1,424
  • 2
  • 15
  • 36

5 Answers5

26

If anyone else has invalid signature errors, you should check this comment : https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/521#issuecomment-577400515

Solved the issue for my configuration.

Essentially, if you are getting access tokens to access your own resource server and not the Graph API, your scopes parameter should be [CLIENT_ID]/.default (and if you are using the access token to access the Graph API, you don't need to validate the token yourself)

Antoine
  • 1,068
  • 10
  • 17
22

Please refer to thread : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609

but if look at the Jwt.Header you will see a 'nonce'. This means you need special processing. Normal processing will fail.

So if nonce includes in access token , validate signature with JWT.io or JwtSecurityToken won't success .

Nan Yu
  • 26,101
  • 9
  • 68
  • 148
  • Thanks, so there is no way for me to validate the access token then? – Jeppe Jul 26 '17 at 07:57
  • Please provide token request (remove sensitive information like tenant &&client id) ,you could use fiddler or browser developer tool to capture the request , i will check why nonce included in header . In addition , you needn't validate the signature of access token for aad graph api . When sending api calls with azure ad access token ,graph api server side will validate it . If you are acquiring token for your own api , you could validate the access token with owin middleware or manually validating the JWT token. – Nan Yu Jul 26 '17 at 08:09
  • is that the final answer? my token contains "nonce" as well :/ – aero8991 May 31 '23 at 20:05
19

Thanks to Nan Yu I managed to get token that can be validated by any public jwt validator like jwt.io (couldn't put my comment in the comments section under Nan Yu's answer because its too long).

So as I understand the point from the discussion mentioned by Nan Yu that by default Azure AD generates tokens for Microsoft Graph and these tokens use special signing mechanism so that it is not possible to validate signature using public validators (except jwt.ms Microsoft's validator which most probably knows what mysterious special handling means :) ).

To get access token not for Microsoft Graph that can be validated using public validators I had to:

  • Remove any Microsoft Graph related scopes (by default I had only one scope configured User.Read so removed it in appConfig > API permissions)
  • create a custom scope for your application (appConfig > Expose an API > Add scope ...) this scope will look like api://{application-id}/scope-name
  • add just created scope in the application API permissions (appConfig > API permissions > Add api permission > My APIs > select your application > Delegated Permissions > Check your scope > Add permission)
  • then use this scope in your openid client scopes, in my case I have: openid offline_access {application-id}/scope-name

Note that in the openid client config newly created scope is used without api:// prefix (offline_access I have to enable refresh_token can be ignored if refresh token mechanism is not used)


Oleh
  • 472
  • 6
  • 13
  • 1
    Creating a custom scope and requesting a token with that scope instead of the "User.Read" solved the problem for me. Thanks. – Dmitry S. Apr 14 '22 at 18:31
  • @Oleh is this what is required if you want to create a backend express server to read tokens coming from msal? I am having trouble authenticating my tokens. – aero8991 May 31 '23 at 19:56
  • 1
    @aero8991 this should be auth library agnostic but I can't tell you what other pitfalls could be with msal specifically. I was creating custom auth library that supports two auth providers (MS Active Directory and IdentityServer4) and in my case on the client side I'm using https://www.npmjs.com/package/openid-client which is working for ~2 years on prod just fine. Maybe msal has something simpler since both AD auth provider and msal client library are from the same vendor and I suppose there is some easy integration but unfortunately can't help you with this. – Oleh Jun 01 '23 at 07:24
6

If you are using msal.js library with react, add this to your auth configuration.

scopes: [`${clientId}/.default`]

Editing scopes fixed issue for me

Giri Aakula
  • 123
  • 2
  • 9
3

Well thanks to @Antoine I fix my code. Here I will let my personal vue.js plugin that is working for everybody else reference:

import { PublicClientApplication } from '@azure/msal-browser'
import { Notify } from 'quasar'

export class MsalService {
  _msal = null
  _store = null
  _loginRequest = null

  constructor (appConfig, store) {
    this._store = store
    this._msal = new PublicClientApplication(
      {
        auth: {
          clientId: appConfig.auth.clientId,
          authority: appConfig.auth.authority
        },
        cache: {
          cacheLocation: 'localStorage'
        }
      })

    this._loginRequest = {
      scopes: [`${appConfig.auth.clientId}/.default`]
    }
  }

  async handleResponse (response) {
    await this._store.dispatch('auth/setResponse', response)
    const accounts = this._msal.getAllAccounts()
    await this._store.dispatch('auth/setAccounts', accounts)

    if (accounts.length > 0) {
      this._msal.setActiveAccount(accounts[0])
      this._msal.acquireTokenSilent(this._loginRequest).then(async (accessTokenResponse) => {
        // Acquire token silent success
        // Call API with token
        // let accessToken = accessTokenResponse.accessToken;
        await this._store.dispatch('auth/setResponse', accessTokenResponse)
      }).catch((error) => {
        Notify.create({
          message: JSON.stringify(error),
          color: 'red'
        })
        // Acquire token silent failure, and send an interactive request
        if (error.errorMessage.indexOf('interaction_required') !== -1) {
          this._msal.acquireTokenPopup(this._loginRequest).then(async (accessTokenResponse) => {
            // Acquire token interactive success
            await this._store.dispatch('auth/setResponse', accessTokenResponse)
          }).catch((error) => {
            // Acquire token interactive failure
            Notify.create({
              message: JSON.stringify(error),
              color: 'red'
            })
          })
        }
      })
    }
  }

  async login () {
    // this._msal.handleRedirectPromise().then((res) => this.handleResponse(res))
    // await this._msal.loginRedirect(this._loginRequest)
    await this._msal.loginPopup(this._loginRequest).then((resp) => this.handleResponse(resp))
  }

  async logout () {
    await this._store.dispatch('auth/setAccounts', [])
    await this._msal.logout()
  }
}

// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/boot-files
export default ({
  app,
  store,
  Vue
}) => {
  const msalInstance = new MsalService(
    app.appConfig, store
  )
  Vue.prototype.$msal = msalInstance
  app.msal = msalInstance
}

PD: using quasar framework

  • 1
    Frontend is usually not supposed to verify tokens. It's not even really supposed to look at it. Because what are you going to do with the knowledge that it's a valid token? You likely want to request data from an API, and the API itself should validate, otherwise, what stops someone from calling your API and claiming the token is valid? Is also stated in the docs. https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens – The Fool Nov 27 '21 at 07:22
  • I added `scopes: [`${clientId}/.default`]` and it works – Giri Aakula Jun 19 '22 at 12:49