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....
User logs into frontend using @azure/msal-react. This currently works.
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.
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]>