Azure AD Authentication : Multiple Scopes Issue - c#

I am likely expecting or doing something that is not correct. Need some help in getting back to the right path.
Simple Usecase - How to configure a client so it can request a token with all the right scopes? currently I am running it via postman but actual client is going to be a react app using msal.
Setup:
App Registration in Azure.
API Permissions:
Microsoft.Graph --> email & User.Read
Exposed an API:
Scope URI: api://someguid
One Scope is Added : api//someguid/testscope
Net Core 6 API
AppSettings.Json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Audience":"api//someguid"
"ClientId": "my-client-id",
"TenantId": "my-tenant-id"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read,email"
}
}
Middleware
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(Configuration.GetSection("Graph"))
.AddInMemoryTokenCaches();
This is how I am calling graph in Controller.
[Authorize]
public class AbcController: Controller
{
private readonly GraphServiceClient _graphClient;
public AbcController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
[HttpGet("get-me")]
public async Task<ActionResult> GetSomeDetails()
{
var user = await _graphClient.Me.Request().GetAsync();
return null;
}
I run this via postman with Auth Code flow with PKCE, Here are the issues
When I set the Scope as : api//someguid/testscope
Call gets authenticated and the Token is acquired correctly in postman
The API get-me get authorized correctly
But the call _graphClient.Me.Request().GetAsync() throws throws a 500 error
Also, a direct call to https://graph.microsoft.com/v1.0/me in postman using the token
gives insufficient privilege error
When I set the scope as : api//someguid/testscope https://graph.microsoft.com/email
Call gets authenticated But the acquire token fails with incorrect scope
When I set the scope as : https://graph.microsoft.com/email https://graph.microsoft.com/user.read
Call gets authenticated and the acquire token is acquired
Direct call to https://graph.microsoft.com/v1.0/me works as expected
But now my API does not get Authorized and gives 401
Can someone suggest what am i missing in my setup or if i am doing something crazy wrong?
All i am looking to do is get my API authorized, and get the email address pulled from graph in the API, without explicitly re-acquiring the token or specifying my client secret in the API to build the graph client.
This was taken as an input to try and build my poc
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-call-api?tabs=aspnetcore

Note that, one token can only be issued to one audience. You cannot acquire access token for multiple audience (Ex: custom API and MS Graph) in single call.
In your scenario, you need to make two separate requests for acquiring access tokens i.e., one for your API and other for Microsoft Graph.
I tried to reproduce the same in my environment via Postman and got below results
I registered one Azure AD application and added same API permissions as below:
Now I exposed one API named testscope same as you like below:
Make sure to select Single-page application while adding Redirect URIs to your application like below:
I acquired token successfully using Auth code flow with PKCE from Postman like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: api://someguid/testscope
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for calling Microsoft Graph /me endpoint and works only to authorize API based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
To call /me endpoint for mail, you need to acquire token again with Microsoft graph scope without configuring client secret like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: https://graph.microsoft.com/.default
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for authorizing API whereas you can call Microsoft Graph based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
When I call /me endpoint using above token, I got the results successfully with mail like below:
References:
Azure AD Oauth2 implicit grant multiple scopes by juunas
reactjs - azure msal-brower with nextJs: passing only one scope to the API

Related

Validate Bearer token (azure ad) with MSAL

I have a aspnet core 6 API and i need in my controller of API, validated the bearer token that with Microsoft Authentication Libraries (MSAL).
I want verify in token, for example, my tenant or clientId.
How i do this simple? I need add notation to verify, for example [AuthorizeMSAL]?
Or i need to call "https://graph.microsoft.com" to validate token?
Example my code:
I receive an exception
The provided value for scope offline_access openid profile is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).
I tried to reproduce the same in my environment and got the same error as below:
The error usually occurs if the scope value is invalid while generating the token.
Note that, Client Credential Grant Type accepts scope with ./default or api://ClientId/.default only.
To resolve the error, use the parameters like below while generating the token:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
grant_type:client_credentials
scope:https://graph.microsoft.com/.default
Access token generated successfully like below:
To validate the Bearer token, use the below sample code in your _Startup.cs_ file as mentioned in this MsDoc:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.TokenValidationParameters.ValidIssuers = new[] { Issuers };
options.TokenValidationParameters.ValidAudiences = new[] { valid};
};
});
The Bearer token will be validated successfully like below:
What you are looking for in here is the Microsoft Identity Platform , which does the job of handling Tokens and authorization in the Bakcend part ( .net web APIs ) . In here I will suppose you have read and done the required configuration on the Azure portal , client app registration , Api registration , and exposing API (via scope or app roles ) if not please refer to the following docs for that.
Then you will need to install Microsft.Identity.Web nuget package to be able to handle the token .In order to make use of the token you need to register the following authentication service to the container inside program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
At this point you should already have and AzureAd section in in your appsetting.json with details from you Azure AD configuration like mentioned below :
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "Enter_the_Application_(client)_ID_here",
"TenantId": "common",
"Audience": "Enter_the_Application_ID_URI_here"
},
}
If the configuration is done right in the Azure portal side and you have the correct credentials at your appsettings.json , you should be able to allow only authenticated users into a specific controller by adding the [Authorize] annotation above the controller definition.
You find here a link to the documentation mentioning all the steps , along with a sample that you can follow on . and this link contains more information about MSAL issued bearer token configuration and validation

Getting OpenID Connect / OAuth access token for calling MS Dynamics

I'm trying to wrap my head around the "modern" auth methods, and dealing with OAuth and access token for calling external services, in ASP.NET Core 5 MVC.
I have an app registration in Azure, which is set up OK - these are the API permissions for that app:
My goal is to call both MS Graph (several calls), and also MS Dynamics365, to gather some information. I've managed to set up authentication with OAuth (or is it OpenID Connect? I'm never quite sure how to tell these two apart) in my MVC app, like this (in Startup.cs):
/* in appsettings.json:
"MicrosoftGraph": {
"Scopes": "user.read organization.read.all servicehealth.read.all servicemessage.read.all",
"BaseUrl": "https://graph.microsoft.com/v1.0"
},
*/
public void ConfigureServices(IServiceCollection services)
{
List<string> initialScopes = Configuration.GetValue<string>("MicrosoftGraph:Scopes")?.Split(' ').ToList();
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddInMemoryTokenCaches();
// further service setups
}
It works fine - I'm prompted to log in, provide my credentials, and in my MVC app, I can check out the claims principals with its claims after logging in - so far, everything seems fine.
Next step is calling the downstream APIs. I studied some blog posts and tutorials and came up with this solution to fetch an access token, based on the name of a scope that I need for a given call. This is my code here (in a Razor page, used to show the data fetched from MS Graph):
public async Task OnGetAsync()
{
List<string> scopes = new List<string>();
scopes.Add("Organization.Read.All");
// fetch the OAuth access token for calling the MS Graph API
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
HttpClient client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("Accept", "application/json");
string graphUrl = "https://graph.microsoft.com/v1.0/subscribedSkus";
string responseJson = await client.GetStringAsync(graphUrl);
// further processing and display of data fetched from MS Graph
}
For the MS Graph scopes, this works just fine - I get my access token, I can pass that to my HttpClient and the call to MS Graph succeeds and I get back the desired info.
The challenge starts when trying to use the same method for getting an access token to call MS Dynamics. I was assuming that I just specify the name of the API permission that is defined in the Azure AD registration - user_impersonation - like this:
public async Task OnGetAsync()
{
List<string> scopes = new List<string>();
scopes.Add("user_impersonation");
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
// further code
}
But now I am getting nothing but errors - like this one:
An unhandled exception occurred while processing the request.
MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID '257a582c-4461-40a4-95c3-2f257d2f8693' named 'BFH_Dyn365_Monitoring'. Send an interactive authorization request for this user and resource.
which is funny, because admin consent has been granted - so I'm not quite sure what the problem is .....
I then figured maybe I needed to add user_impersonation to the list of initial scopes (as defined in the appsettings.json and used in the Startup.ConfigureServices method) - but adding this results in another funny error:
An unhandled exception occurred while processing the request.
OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'AADSTS650053: The application 'BFH_Dyn365_Monitoring' asked for scope 'user_impersonation' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
Strange thing is - as you saw in the very first screenshot here - that scope IS present on the app registration - so I'm not totally sure why this exception is thrown....
Can anyone shed some light, maybe from experience of calling MS Dynamics using an OAuth token? Is this just fundamentally not possible, or am I missing a step or two somewhere?
Thanks! Marc
To get a token for user_impersonation for Dynamics (instead of Microsoft Graph), you should use the full scope value: "{your CRM URL}/user_impersonation".
The full format for the values in the scopes parameter in GetAccessTokenForUserAsync is {resource}/{scope}, where {resource} is the ID or URI for the API you're trying to access, and {scope} is the delegated permission at that resource.
When you omit the {resource} portion, the Microsoft Identity platform assumes you mean Microsoft Graph. Thus, "Organization.Read.All" is interpreted as "https://graph.microsoft.com/Organization.Read.All".
When you attempt to request a token for "user_impersonation", the request fails because such a permission has not been granted for Microsoft Graph. In fact, such a permissions doesn't even exist (for Microsoft Graph), which explains the other error you see.

How to write a message to Microsoft Teams with Azure ClientApplication Token

I try to send a message to Microsoft Teams with the graph api and a non-personal ClientApplication token.
It is currently no problem to get the token from Azure by clientId / secret / tenantId. The token seems to work as well, so I'm able to get e.g. the list of all azur users by rest api. But when I try to send a message to Teams with this token (POST https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages), the result is
{
"error": {
"code": "Unauthorized",
"message": "Unauthorized",
"innerError": {
"date": "...",
"request-id": "..."
}
}
}
The same call works fine with my personal bearer token.
Any idea how to enable the clientapp to post to teams?
From your description, you are using client credential flow to get the token, then use the token to call Microsoft Graph, if so, the Application permission is needed.
You got the error because the Microsoft Graph - Create chatMessage in a channel does not support the Application permission, it means you could not use the client credential flow in this case.
FAQ:
The token seems to work as well, so I'm able to get e.g. the list of all azure users by rest api.
To call different APIs via this flow, you need different Application permission, this works because your app has the application permission to call the api - List users.
The same call works fine with my personal bearer token.
I suppose you are using another flow to get the token, e.g. auth code flow, ropc flow. These flows need the Delegated permission, which is supported to create chatMessage in a channel.

Why doesn't AcquireTokenByAuthorizationCode return RefreshToken

In this documentation it gives a complete flow for a web application that calls a web API:
The web application executes a policy and the user completes the user experience.
Azure AD B2C returns an (OpenID Connect) id_token and an authorization code to the browser.
The browser posts the id_token and authorization code to the redirect URI.
The web server validates the id_token and sets a session cookie.
The web server asks Azure AD B2C for an access_token by providing it with the authorization code, application client ID, and
client credentials.
The access_token and refresh_token are returned to the web server.
The web API is called with the access_token in an authorization header.
The web API validates the token.
Secure data is returned to the web application.
Looking at 6. and using the code in the Azure-Samples repository active-directory-b2c-dotnet-webapp-and-webapi
, I cannot get the line
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
to return a refresh_token. It returns an IdToken and AccessToken but no RefreshToken.
By using my browser and Postman and following the steps in this document with the same B2C tenant and application I do get the refresh token as expected.
This question is similar to mine and the blog post mentioned in one of the answers provides a work around to the symptom of not having a refresh token but my question remains:
How can I get AcquireTokenByAuthorizationCode to return a refresh_token?
To get refresh token, your application should append offline_access as scope.
You mentioned like this msdn able to return you refresh token. It is because request already contain offline_access scope
&scope=openid%20offline_access
To get refresh token from active-directory-b2c-dotnet-webapp-and-webapi. You need to update Global.cs Scopes filed to include offline_access
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, "offline_access" };
The offline_access scope is optional for web apps. It indicates that your app needs a refresh token for long-lived access to resources.
Go to web.config add below:
<add key ="api:OfflineAccessScope" value="offline_access "/>
And in Global.cs :
public static string OfflineAccessScope = ApiIdentifier + ConfigurationManager.AppSettings["api:OfflineAccessScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, OfflineAccessScope};
Then the Globals.Scopes in AcquireTokenByAuthorizationCode will return refresh token.

Azure AD Authentication With Auth0

I created an Azure API App and set the [Authorize] attribute on my controller and published it to Azure. Then I registered an Auth0 app, providing the data from my AD app, following Auth0's documentation. When I test the connection on their site it works just fine. I can also log in fine in my app and retrieve the access token with:
var auth0 = new Auth0Client("myUrl", "myTenant");
var user = await auth0.LoginAsync();
var accessToken = user.IdToken;
However, when I make a request to my API, passing the access token in the Authorization header, it just throws an error 401 (Unauthorized). From what the documentation says, I was under the impression this is all that should be needed, and I've not found any information to suggest otherwise. Is there an additional step to linking these?
The Solution is to configure ur API to accept tokens by that issuer, like for example by using owin middleware app.UseJwtBearerAuthentication(). Glad I could help!

Categories

Resources