Invalid signature while validating Azure ad access token, but id token works

13,150

Solution 1

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 .

Solution 2

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)

Solution 3

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)


Solution 4

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

Share:
13,150
Jeppe
Author by

Jeppe

By day: Beginner Full stack developer at Topo Solutions By night: Hacking away on random electronics projects or out eating new wonderfully weird food. For fun: Learning some random new skill online, or cooking/baking the next thing I have never tried before

Updated on June 17, 2022

Comments

  • Jeppe
    Jeppe almost 2 years

    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?