MSAL SPA and Python Flask on behalf of "Invalid Audience" Response

Alec Sanchez 96 Reputation points
2021-08-16T23:13:34.783+00:00

Hello!

Thank you for taking the time to read this post.

I'm currently developing a full stack application, and an attempting to use an on behalf of flow to call some azure graph APIs. I'm currently able to retrieve a token on behalf of the user, but am receiving a response stating the token has an invalid audience when attempting to call a graph API using the on behalf of token. The audience for the on behalf of token is for azure APIs (https://management.azure.com), not graph APIs (confirmed on https://jwt.ms).

Can someone please provide any assistance on properly calling an azure graph API on behalf of an SPA client, using Python/Flask?

  • Frontend: Javascript/React
  • Backend: Python/Flask

The idea is....

  1. User logs into frontend using @azure/msal-react. This currently works.
  2. User invokes an API call to the Python/Flask application to return information from a mysql database (on-premise stuff). This currently works, and we are able to protect our APIs using azure's tokens.
  3. User invokes an API call to the Python/Flask application, which requests a token on behalf of the user, to call an Azure Graph API, in this case User.ReadBasic.All. This does not work at this time, we are getting a response from Flask stating 'message': 'Access token validation failure. Invalid audience.'

Some additional notes regarding our current setup.

  • The React SPA, and Python Flask Backend are each configured as their own azure application.
  • The frontend application was added as a known application for the backend in Azure Scopes.
  • In the manifest for both applications, accessTokenAcceptedVersion has been changed to 2
  • We do not have "Access tokens" or "ID tokens" checked in authentication for the frontend or backend
  • Below is the function called each time the user invokes an API call to the backend

export const getToken = async (graphToken = false) => {

const pca = new PublicClientApplication(msalConfig)
const accounts = pca.getAllAccounts()
const account = accounts[0]

//msalLoginScope.scope = api://<client-id-of-python-flask-application>/.default
//graphConfig.graphmeEndpoint = https://graph.microsoft.com/.default
//If true, use the above 'graphmeEndpoint' scope to return an access token for all available scopes the user may have
//Otherwise return a token for the backend's application

const resp = await pca.acquireTokenSilent({
    scopes: graphToken ? graphConfig.graphMeEndpoint : msalLoginScope.scopes,
    account,
})
console.log("Returning access token")

return resp.accessToken

}

  • This is the function for the backend API on behalf of
    current_access_token = request.headers.get("Authorization", None)
    
    
    if current_access_token is None:
        print("Could not find access token in header")
        raise AuthError({"code": "invalid_header","description":"Unable to parse authorization"" token."}, 401)
    
    
    #SCOPE = "https://management.azure.com/user_impersonation"
    #acquire token on behalf of the user that called this API
    arm_resource_access_token = AuthenticationHelper.get_confidential_client().acquire_token_on_behalf_of(
        user_assertion=current_access_token.split(' ')[1],
        scopes=[current_app.config.get("SCOPE")]
    )
    
    if "error" in arm_resource_access_token:
        raise AuthError({"code": arm_resource_access_token.get("error"),"description":""+arm_resource_access_token.get("error_description")+""}, 404)
    
    headers = {'Authorization': arm_resource_access_token['token_type'] + ' ' + arm_resource_access_token['access_token']}
    
    #MSAL_API_USER_READ_BASIC_ALL = "https://graph.microsoft.com/User.ReadBasic.All"
    subscriptions_list = req.get(current_app.config.get("MSAL_API_USER_READ_BASIC_ALL"), headers=headers).json()
    
    return jsonify(subscriptions_list) <-- This returns the message stating invalid audience.
    

Full response
<class 'dict'>
{'error': {'code': 'InvalidAuthenticationToken', 'message': 'Access token validation failure. Invalid audience.', 'innerError': {'date': '2021-08-16T22:38:08', 'request-id': 'a client id', 'client-request-id': 'a client id'}}}
<Response 325 bytes [200 OK]>

Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
22,160 questions
{count} votes

Accepted answer
  1. Alec Sanchez 96 Reputation points
    2021-08-19T13:33:02.7+00:00

    I've finally got something working.

    1. Regarding the original "Invalid Audience" response, the client should be sending the backend (or middle tier API) a token FOR THAT APPLICATION.
    2. The scope in my backend function to request the ObO token, is the scope required by the downstream API. This is returning a version 1.0 token, my current understanding is single tenant applications return a 1.0 token, from here I am able to call v1.0 APIs referenced here https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0

    If you're looking to recreate these helper functions, please take a look at this github repo, there is a folder for both the frontend (django in the github case) and backend (python/flask).

    https://github.com/Azure-Samples/ms-identity-python-on-behalf-of


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.