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]>