I am passing an accessToken from a front-end application to a back-end application. My back-end application is a .Net WebAPI and my front-end application is a Blazor WebAssembly application. I am using the /.auth/me endpoint on the WebApp to get the access_token of the logged in user and am passing it as a Bearer token when calling my WebAPI however I am getting a 401 Unauthorized when attempting to make the API call. I have my setup based on these docs:
https://learn.microsoft.com/en-us/azure/app-service/tutorial-auth-aad?pivots=platform-windows
The only difference is that in my back-end app registration I have a custom scope named ‘API.ReadOnly’ (rather than the default one). I have given the front-end app registration delgated permisions as well as granted admin consent to that scope.
Here is how I am accessing the token:
var msAuthenticationJson = new Portal.Models.Authentication.MicrosoftIdentityAuthenticationJson();
var authMeResults = await Http.GetFromJsonAsync<Portal.Models.Authentication.MicrosoftIdentityAuthenticationJson[]>("/.auth/me");
msAuthenticationJson = authMeResults[0];
var accessToken = msAuthenticationJson.access_token;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
data = await httpClient.GetFromJsonAsync<Data[]>("https://mywebapp.azurewebsites.net/endpoint");
In my network inspector the api call and the authentication header look fine. If I copy the token into jwt.io or jwt.ms it decodes as expected and shows my user info and available scopes. I should also note that I can browse to either of the WebApps in my web browser and it logs me in with my Microsoft account as expected. I also have CORS set up properly.
I think where I am having issues is in the Configure App Service to return a usable access token step.
I ran the CLI example:
authSettings=$(az webapp auth show -g myAuthResourceGroup -n <front-end-app-name>)
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.login += {"loginParameters":["scope=openid profile email offline_access api://<back-end-client-id>/API.ReadOnly"]}')
az webapp auth set --resource-group myAuthResourceGroup --name <front-end-app-name> --body "$authSettings"
But my results seem different from what the docs suggest they should be. I only see the API.ReadOnly scope and do not see openid, profile or email. Here is what the decoded access_token looks like:
I’m also wondering if there is a better way to configure the App Service to return the proper scopes. Having to use the Azure Cloud Shell to configure every front-end site like this seems clunky. I am surprised there isn’t a cleaner command that does not require parsing, manipulating and updating the entire JSON file. Is there a more elegant way? Is there a way to handle this in the portal?
I also read that the az webapp auth set command updates the "auth.json" file. But where can I find this? I FTP’d into the site to browse all the files and used Kudu but don’t see this anywhere!
This aspect of the authentication configuration is a real blind spot for me. Would love to have some light shed on it!
UPDATE 1
I have been continuing to do some research on this and came across this document outlining how you can manually edit the JSON of the authsettingsV2 settings using resources.azure.com. When I looked at the settings on my front-end app they look correct:
So is my access_token fine even though I don’t see the additional scopes represented as I do in resource explorer? If so why would I still be getting 401 Unauthorized?
UPDATE 2
I turned on App Service Logs on my back-end service and am able to get a more detailed message when attempting to access my API endpoint from the front-end:
HTTP Error 401.83 - Unauthorized
You do not have permission to view this directory or page.
Most likely causes: The authenticated user does not have access to a resource needed to process the request.
Module: EasyAuthModule_32bit
I also took a closer look at the access_token and verified that the audience is correct: "aud": "api://(back-end-app-reg-guid)",
UPDATE 3
After doing some trial and error I added a record for "Allowed token audiences" in the edit page for the registered Authentication provider of the back-end app. I used "api://{backend-app-registration-id}" and it is now properly authenticating my access_token which is issued this as the "aud" value.
It is very strange to me that I need to do this as it is not in the documentation and I am just using the ID of the already registered app so you would think this would be allowed by default since it is just referencing itself!
Going to add this as an answer for anyone else that comes across the issue.
2
Answers
After doing some trial and error I added a record for "Allowed token audiences" in the Edit identity provider page for the registered Authentication provider of the back-end app. I used "api://{backend-app-registration-id}" and it is now properly authenticating my access_token which is issued this as the "aud" value.
What is strange to to me is that I need to do this. It is not in the documentation and I am just using the ID of the already registered app (see screenshot above) so you would think this would be allowed by default since it is just referencing itself!
The only thing that comes to mind is that this extra step is required because I am using a custom scope and the documentation I was following assumed the default user_impersonation scope was being used.
Hope this helps anyone else that comes across a similar issue!
UPDATED
After a little more research I found out that the "Allowed token audiences" record is added for you automatically if you choose to have the app-service generate a new app-registration on your behalf.
Since I had generated my own app-registrations ahead of time and used the "Use existing app registration" flow, this record is not added and you have to manually add it yourself.
This did not appear in the documents since the walkthrough uses the "Create new app registration" flow and did not offer details should you have chosen the alternative path.
turned on, which may cause the Web Api not to accept any requests
via the Authentication header.
Please try by disabling the option
.service, please recheck if it is given secret value and not its Id by
chance.
:
<app-url>/.auth/login/aad/callback
Please check reference 2 for
possible causes.
Docs