Search code examples
azureazure-active-directoryjwtaccess-tokenazure-ad-graph-api

Is there a way to verify an azure access token for Microsoft Graph API at the backend?


I have an Azure app configured for my frontend. From that, I am obtaining an access token from Azure for the Microsoft Graph API. This is my frontend auth config.

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

const msalConfig = {
  auth: {
    clientId: '#####',
    authority:
      'https://login.microsoftonline.com/#####', 
    redirectUri: '#####'
  },

  cache: {
    cacheLocation: 'localStorage', 
    storeAuthStateInCookie: true 
  }
};


export const loginRequest = {
  scopes: ['User.Read', 'User.ReadBasic.All']
};

const msalInstance = new PublicClientApplication(msalConfig);

This is how I obtain access token at the frontend.

import { msalInstance, loginRequest } from './auth.config';
const accounts = msalInstance.getAllAccounts();
if (accounts.length <= 0) return;
try {
   const response = await msalInstance.acquireTokenSilent({
     ...loginRequest,
      account: accounts[0]
   });
   // response.accesstoken is obtained
} catch (error) {
   console.error(error);
}

When I am calling the backend API, I send this token as a bearer token in the request to backend and I need to verify it at the backend. This is where I am stuck. I tried but I cannot verify this token at the backend.

Since this access token is a jwt token I try to verify it in the follwing way.

import jwt, { JwtPayload } from 'jsonwebtoken';
import { IPublicKey } from './interfaces';
import jwkToPem from 'jwk-to-pem';

async function getKeysUri(): Promise<string> {
    // Get jwks_uri from following meta data url.
    // https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration?appid=###
}

async function getKeyForKid(kid: string, jwksUri: string): Promise<IPublicKey> {
    // Get keys from jwksUri
    return keys.find((obj: IPublicKey) => obj.kid === kid);
}

export async function verifyAccessToken(accessToken: string): Promise<JwtPayload> {
    try {
        const decoded = jwt.decode(accessToken, { complete: true });
        const kid = decoded.header.kid;
        const jwksUri = await getKeysUri();
        const key = await getKeyForKid(kid, jwksUri);
        const pem = jwkToPem(key);
        const payload = jwt.verify(accessToken, pem) as JwtPayload;
        return payload;
    } catch (err) {
        console.error(err)
    }
}

In this way, I can obtain the public key corresponding to the private key which was used to signing the token and I should be able to verify the token. But this fails by giving me an error message saying "Invalid signature". I cannot find why it is failing. (I already checed whether the public key I am obtaining using jwkToPem and it is correct). I need help to verfiy this token or find the error in above code snippet.


Solution

  • You should not be verifying tokens that are not meant for your API. Graph API also uses some different technique for their tokens, which you see here. That isn't a problem since only Graph API itself needs to be able to verify the token.

    Your front-end should acquire an access token for your API. That one you can verify, and then use the on-behalf-of flow to exchange that for a Graph API token, which you can then use to call Graph API.