With ADAL.js use of ClientId as Audience in Endpoints for SPA config - azure-active-directory

I am creating an AngularJS client to interact with two ASP.Net Core (v2.0) APIs, all of which are to be secured with Azure AD. Because of a requirement to use roles and groups we will be using the v1 endpoint and therefore ADAL.js. The UI client must be a separate project from each API, additionally the UI project is not a Visual Studio project, rather VSCode with npm. In the UI project I am using:
AngularJS 1.6.9
UI-Router 1.0.15
ADAL.js 1.0.17
After a long time of trial and error, I finally got the UI to authenticate to the API after I took the following steps:
In the UI project I included endpoints in the Adal init() function:
var endpoints = {
'http://localhost:8000/api0/': '<API_0_CLIENT_ID HERE>',
'http://localhost:8001/api1/': '<API_1_CLIENT_ID HERE>',
};
adalAuthenticationServiceProvider.init(
{
tenant: 'slurm.onmicrosoft.com',
clientId: '00000000-0000-0000-0000-XXXXXXXXXX',
endpoints: endpoints
},
$httpProvider
);
There are two endpoints, one is the base url for each API, and each has the corresponding clientId for that API as assigned when each api was registered in Azure AD. Also, the UI project, when registered in Azure AD, is granted appropriate permissions to each api. Once I did set these endpoints in the init() function, and the UI had each clientId of the APIs, the UI was able to authenticate properly to both APIs. This is the SO question that finally clued me in to try this: 32352325
If I do not provide the endpoints in the UI project, a token is not even passed back to the API and therefor authentication fails.
What I am not sure of (there is no clear documentation), is whether the UI clientId should be set as the audienceId in each API or keep each API client id embedded in the UI.
Questions:
1) For an AngularJS UI project that is separate from each API, and each project is registered separately in Azure AD, do we register the UI clientId as an audience with each API or allow the client to know each API clientId?
2) Why are endpoints seemingly required to be specified in the ADAL init() function on the client? Or am I using them incorrectly?
Based on the ReadMe of this Azure-Samples project, it would appear the UI should know the clientId of each API (under Step 3, Configure the WebApp it says):
In the TodoListWebApp project, //...// Find the TodoListResourceId property and replace the value with the Application ID of the TodoListService app
But, this example is not an SPA example and therefore does not use the implicit flow.
Much thanks in advance!

This is what I eventually deduced (after lots of digging and trial and error). Again the example assumes the UI is a separate project from any APIs. For the original questions above:
1) For an AngularJS UI project that is separate from each API, and
each project is registered separately in Azure AD, do we register the
UI clientId as an audience with each API or allow the client to know
each API clientId?
The Client UI knows about each API but uses the App ID URI
2) Why are endpoints seemingly required to be specified in the ADAL
init() function on the client? Or am I using them incorrectly?
Read on.
For each API your UI will access, there needs to be an endpoint declared in the endpoints map structure. For example, say I am trying to access 2 separate APIs registered in AAD with the following relevant info:
Tenant: slurm.onmicrosoft.com
API_0
Home Page URL: 'https://localhost:8000'
App ID URI: 'https://slurm.onmicrosoft.com/00000000-0000-0000-0000-aaaaaaaaaaaa'
API_1
Home Page URL: 'https://localhost:4000'
App ID URI: 'https://slurm.onmicrosoft.com/00000000-0000-0000-0000-bbbbbbbbbbbb'
adalAuthenticationServiceProvider.init(
{
tenant: 'slurm.onmicrosoft.com',
clientId: '00000000-0000-0000-0000-XXXXXXXXXX',
endpoints: {
'localhost:8000': 'https://slurm.onmicrosoft.com/00000000-0000-0000-0000-aaaaaaaaaaaa',
'localhost:4000': 'https://slurm.onmicrosoft.com/00000000-0000-0000-0000-bbbbbbbbbbbb',
}
},
$httpProvider
);
The following are the references:
Go to http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1
Scroll to section Calling API via CORS
Follow the link to the github example:
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp-dotnet-webapi
On the github example readme
Go to Step 3: Configure the To Go API to use your Azure Active
Directory tenant
See step #7 which states:
Enter a mapping of the To Go API endpoint location to its resource
identifier, or App ID URI. The name of the property of the endpoints
object should be the location of the To Go API.
Also if you look into the code example you will see in the web.config of the ToGoAPI it indicates the 'Audience' value is set to the App ID URI of the ToGoAPI
Note: The App Id URIs used above are intentionally left in a format similar to the default values Azure AD will
provide when you register an application. These can be changed (just make sure you change it everywhere).
Note 2: In the endpoints map you see that the keys do not include the scheme and to not fully match the corresponding Home Page URL. When I included the scheme i.e https:// I got 401 responses from the API

Related

401 Unauthorized calling my Azure Rest API

I have a Rest API using controllers, etc, hosted in Azure that has been working for some time. I want to secure the various methods. I added the API App (.NET core) to the App Registrations, and also added the javascript client app to App Registrations. I believe I'm initializing everything in startup.cs in the REST Api OK. I added [Authorize] to one of the methods. I used a simple javascript example which calls myMSALObj.loginPopup, and gets back a token which I then add to the Authorization header and make a fetch call. When I call, I see HTTP Error 401.0 - Unauthorized in the log stream for my App Service.
Any ideas how I can troubleshoot this to get more specifics about what is wrong?
Also, a related question: in App Registrations, Api Permissions, how does one correlate the API permission name with the method in the controller?
Add this in front of the method in the controller
[AuthorizeForScopes(Scopes = new[] { "My.Scope" })]

Azure Active Directory - UI > API - 401 Error

Good Day,
Currently I have a single tenent with a React UI and .NET Core Apis secured by Azure Active Directory without any problems.
We have recently moved to a new Azure Tenent, new Active Directory etc. I have create two new App Registrations, one single App Service for UI and one for API. I have linked the App Service to AAD (UI = UI App Registration, API = API App Registration).
The problem is the API is getting a 401 error and I think see that in the original tenent the Bearer token is in a JWT format but in the new instance it's not, I believe it my be a graph api access key.
New Tenent:
Authorization: Bearer PAQABAAAAAAD--DLA3VO7QrddgJg7WevrQvEQVbZEMD8su-tIp9k2bTFUTort7SZgeDI52P6KRYefHgtmj4YrecgUKZJ2wylGuhvIzIz642n7Sg0VMU1RwKtrzWlaMqK62CaSoJcstxiEf6 *****
Orginal Tenent:
Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiI3OThkN2ZkOC0zODk2LTQxOGMtOTQ0Ny0wNGFlNTQ2OGFkNDIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83ZDE3NTU3Ni03Y2Y3LTQyMDctOTA5My0wNmNiNmQyZDIwNjAvIiwiaWF0IjoxNjE2NDUyNzExLCJuYmYiOjE2MTY0NTI3MTEsImV4cCI6MTYxNjQ1NjYxMSwiYWNyIjoiMSIsImFpbyI6IkFTUUEyLzhUQUFBQU9mejhPZHp *****
Please someone kindly enought to provide some guidance / input where I am going wrong.
Regards
Paul.
When using Azure AD to obtain an access token, an additional resource parameter is required. Otherwise, the access token is not a JWT.
For example, if your web API's application ID URI is https://contoso.com/api and the scope name is Employees.Read.All, then with oidc-client the client configuration should be :
scope: 'openid profile email Employees.Read.All',
extraQueryParams: {
resource: 'https://contoso.com/api'
}
In App Service auth configuration, you can use additionalLoginParams
"additionalLoginParams": ["response_type=code", "resource=https://contoso.com/api"]
If you did not use a custom application ID URI, it may look like
api://868662dd-3e28-4c7f-b7d5-7ec02ac9c601
Quickstart: Configure an application to expose a web API
Firstly, the scope is incorrect.
You should Expose an API in your API App Registration and then add it as a permission in your UI App Registration. You can refer to this document.
And when you try to call the 'https://login.windows.net/{tenant}/oauth2/authorize endpoint, you need to specify the scope to include api://{app id of the API App Registration}. For example: api://{app id of the API App Registration} openid profile email. Then the access token would be for calling your API.
At last, for CORS issue, please configure the CORS as * in your web app to see if it helps.
Try to follow this step: Configure App Service to return a usable access token
In my experience, this problem occurs, when you try to authorize against version 1 of the endpoint.
Instead of calling
https://login.microsoftonline.com/{tenant}/oauth2/authorize
call
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
You might be required to set something like "metadata URL" in you authorization library to:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/.well-known/openid-configuration
Make sure your builder follows this order...lifted from our API program.cs
These must be in order of
UseRouting -> UseAuthentication -> UseAuthorisation -> MapControllers
> app.UseRouting()
> app.UseAuthentication()
> app.UseAuthorization()
> app.MapControllers()
If app.UseAuthentication and app.UseAuthorization are not in this order in statement position you Will get 401 Unauthorised as at 01/2023 .Net 6 Core.

Blazor WASM calling Azure AAD secured Functions API

I have an Azure Functions API which uses Azure Active Directory authentication. I can test locally and deployed using a browser and curl calls in a process of:
Get a code
Use the code to get a token
Pass the token to authenticate and get the function result.
I now want to call this API from my Blazor WASM app but I'm sure there must be a nice MSAL call to do all the authentication but I cannot find any documentation on what that might be.
Does anyone have a code snippet to illustrate what needs to happen?
Further Information
My Azure Functions App and Blazor WASM client are not part of the same project and are hosted on different sub-domains of Azure hypotheticalapi.azurewebsites.net and hypotheticalweb.azurewebsites.net.
The web client application registration has API Permissions for the API and the API has an application registration which exposes itself with the scope that the client app has permissions for.
Again, the API and Web app work individually. I just don't seem able to get them to talk.
I have been following the "ASP.NET Core Blazor WebAssembly additional security scenarios" documentation but after several attempts I keep coming back to the error:
Microsoft.JSInterop.JSException: invalid_grant: AADSTS65001:
The user or administrator has not consented to use the application with ID 'e40aabb0-8ed5-4833-b50d-ec7ca4e07996' named 'BallerinaBlazor5Wasm'.
Send an interactive authorization request for this user and resource.
Even though I have revoked/deleted the client's permissions on the API, it has never repeated asking for consent. Is there a way I should clear the consent I previously gave? No idea how I might do that.
This GitHub Issue appears to be relevant.
I was stuck for the last two weeks with the same error code in the same setting: Blazor WASM talking to an AAD secured Azure Functions app.
What appeared to be a problem in my case was the scopes that I was listing in the http request when contacting AAD identification provider endpoints. Almost all examples I came across use Microsoft Graph API. There, User.Read is the scope that is given as an example. My first though was that even when I am contacting my own API I have to include the User.Read scope in the request because I was reasoning that this scope is necessary to identify the user. However, this is not the case and the only scope that you have to list when you call the authorize and token endpoints is the one that you exposed under the "Expose an API blade" in your AAD app registration.
I am using the OAuth2 authorization code in my example and not the implicit grant. Make sure that in the manifest of your API registration you have set "accessTokenAcceptedVersion": 2 and not "accessTokenAcceptedVersion": null. The latter implies the use of implicit flow as far as I know.
The scope the I exposed in my API is Api.Read. You can expose more scopes if you need but the point is that you only ask for scopes that you exposed.
I also have both following options unticked (i.e. no implicit flow). However, I tried with selecting "ID token" and it still worked. Note that the "ID token" option is selected by default if you let the Azure Portal create your AAD app registration from your function app Authentication blade.
Blazor code
Program.cs
This code has to be added.
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("{NAME}",
client => client.BaseAddress = new Uri("https://your-azure-functions-url.net"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{NAME}"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
// NOTE: no "api://" when providing the scope
options.ProviderOptions.DefaultAccessTokenScopes.Add("{you API application id}/{api exposed scope}");
});
appsetting.json
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{aad tenant id}",
"ClientId": "{application id of your blazor wasm app}",
"ValidateAuthority": true
}
GraphAPIAuthorizationMessageHandler.cs
Note that this class can have a different name. you'll then also reference a different name in Program.cs.
public class GraphAPIAuthorizationMessageHandler : AuthorizationMessageHandler
{
public GraphAPIAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://your-azure-functions-url.net" },
// NOTE: here with "api://"
scopes: new[] { "api://{you API application id}/{api exposed scope}" });
}
}
I hope this works. If not, let me know.
At least you need to get the access token, then use the token to call the function api. In this case, if you want to get the token in only one step, you could use the client credential flow, MSAL sample here, follow every part on the left to complete the prerequisites.
The following are the approximate steps(for more details, you still need to follow the sample above):
1.Create a new App registration and add a client secret.
2.Instantiate the confidential client application with a client secret
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
3.Get the token
string[] scopes = new string[] { "<AppId URI of your function related AD App>/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
4.Call the function API
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

Trying to access a v2 endpoint hosted webapi but no luck, true if only graph api works on v2 now?

Had a webapi running on v2 endpoint, the intent was to get access through a single call to both graph and the custom webapi, was using the v2 auth code grant flow, the url using as below,
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=14e9111f3e1-d220-415d-9bf4-d089f0b5feff&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A8081%2Fartifactory%2Fwebapp%2Fsaml%2FloginResponse&response_mode=query&scope=api%3A%2F%2F14e9f3e1-d220-415d-9bf4-d089f0b5feff%2Faccess_as_user%20https%3A%2F%2Fgraph.windows.net%2Fuser.read%20openid%20offline_access&state=12345
with the scope as
api://14e9f3e1-d220-415d-9bf4-d089f0b5feff/access_as_user https://graph.windows.net/user.read openid offline_access
However, keep failing with a invalid scope error. If I take out the custom webapi from the resource, everything went through wonderfully.
Reading further, there is a limitation for webpi that
Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID.
So I am confused, how to archieve the goal to use v2 endpoint to authenticate and get access to both graph and webapi????
--edit
the error message is 'AADSTS65005: The application 'blah' asked for scope 'user.read' that doesn't exist on the resource. Contact the app vendor.'
Today the v2 endpoint cannot issue an access token for a custom API. The feature is in active development, but there's no ETA to share.
Also note: even when the feature will be available, you will not be able to reuse the same access token across multiple resources; you'll be able to consent for multiple resources at once, so that your user is only promoted once, but you will need to request access tokens for each resources separately.

WebAPI registered in new v2 endpoint does not prompt consent screen

An app registered in the v2 app registration portal, with a webapi platform added, however when requesting the authorisation with api scope using https://login.microsoftonline.com/common/oauth2/v2.0/authorize endpoint, it directly go to the redirect url with error code AADSTS70002%3a+Error+validating+credentials. Instead if I use the graph api, everything works, not sure what did I did wrong!
The one for the webapi scope:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=222db0-0305-4f86-96cc-d283da2f5020&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%2Ftest&response_mode=query&scope=openid%20offline_access%20api%3A%2F%2Fc6599db0-0305-4f86-96cc-d283da2f5020%2Faccess_as_user
The one for the graph api:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=222db0b0-0305-4f86-96cc-d283da2f5020&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%2Ftest&response_mode=query&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
EDIT
This is confusing, now I put the offline and open id to the end but get another error:
AADSTS65005%3a+The+application+'v2app'+asked+for+scope+'openid'+that+doesn't+exist+on+the+resource.+Contact+the+app+vendor.
The URL I requested is:
https://login.microsoftonline.com/e0582222da8-ec2b-4c7a-b98e-2c417627657c/oauth2/v2.0/authorize?client_id=c6599db0-0305-4f86-96cc-d283da2f5020&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%2Ftest&response_mode=query&scope=api%3A%2F%2Fc6599db0-0305-4f86-96cc-d283da2f5020%2Faccess_as_user%20openid%20offline_access
And if I google this error, there is one saying:
As of today, the V2 Endpoint does not support API access other than the Microsoft Graph.
So this is not achievable? Then how come the sample works?
EDIT 2
So if I make the requested scope the client ID, it works..
I reproduced your error . but if putting the openid%20offline_access scopes after api%3A%2F%2Fc6599db0-0305-4f86-96cc-d283da2f5020%2Faccess_as_user(api scope) , it works on my side , please try :
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=222db0-0305-4f86-96cc-d283da2f5020&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%2Ftest&response_mode=query&scope=api%3A%2F%2Fc6599db0-0305-4f86-96cc-d283da2f5020%2Faccess_as_user%20openid%20offline_access
Update
The MSAL error you linked is accessing a Web API from a client that has a different Application ID , that is not supported in v2.0 endpoint . In azure ad v2.0, Web API can receive tokens only from an application that has the same Application ID. That means api://xxxx-xxxx-xxx-xxx (web api url) , the xxxx-xxxx-xxx-xxx part has the same value with your client id of client application . To achive that , you could add two platforms : one web app ,one web api .

Resources