I have a ASP.NET Core 3.1 project like this sample: Sign-in a user with the Microsoft Identity Platform in a WPF Desktop application and call an ASP.NET Core Web API.
I'm using Identity web
version 1.0 and Azure AD, single-tenant application.
I've edited the manifest adding appRoles
since I'm requesting an application token only, and not a user token:
[... more json ...]
"appId": "<guid>",
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Accesses the application.",
"displayName": "access_as_application",
"id": "<unique guid>",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "access_as_application"
}
],
"oauth2AllowUrlPathMatching": false,
[... more json ...]
I've also enabled the idtyp
access token claim, to specify that this is an application token.:
[... more json ...]
"optionalClaims": {
"idToken": [],
"accessToken": [
{
"name": "idtyp",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"saml2Token": []
[... more json ...]
The following request is made with Postman. Please notice the use of /.default
with the scope, which is mentioned in the documentation in relation to the client credentials grant flow.
POST /{tenant_id}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
scope=api%3A%2F%2{client_id}%2F.default
&client_id={client_id}
&grant_type=client_credentials
&client_secret={secret_key}
The request returns an access_token
which can be viewed with jwt.ms and looks like this, where actual data have been replaced by placeholders for security reasons.:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "[...]",
"kid": "[...]"
}.{
"aud": "api://<client_id>",
"iss": "https://sts.windows.net/<tenant_id>/",
"iat": 1601803439,
"nbf": 1601803439,
"exp": 1601807339,
"aio": "[...]==",
"appid": "<app id>",
"appidacr": "1",
"idp": "https://sts.windows.net/<tenant_id>/",
"idtyp": "app",
"oid": "<guid>",
"rh": "[..].",
"roles": [
"access_as_application"
],
"sub": "<guid>",
"tid": "<guid>",
"uti": "[...]",
"ver": "1.0"
}
I notice that the token above does not include scp
. This seem correct as this is an application token and not a user token. Instead it includes `”roles”´ as appropiate for an application token.
The access_token
can now be used as bearer in a Postman Get:
GET /api/myapi
Host: https://localhost:5001
Authorization: Bearer {access_token}
The reponse to this request is 500 internal error
. I.e. something is wrong. The access_token
looks like a corrent application token, so the error seems to be on the ASP.NET Core 3.1 controller side.
The ASP.NET Core 3.1. project hosting the custom API, has a startup.cs
which includes the following code:
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
// This is added for the sole purpose to highlight the origin of the exception.
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
if (context.Principal.Claims.All(x => x.Type != ClaimConstants.Scope)
&& context.Principal.Claims.All(y => y.Type != ClaimConstants.Scp)
&& context.Principal.Claims.All(y => y.Type != ClaimConstants.Roles))
{
// This where the exception originates from:
throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
}
};
});
The appsettings.json
for the project includes:
"AzureAD": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.onmicrosoft.com",
"ClientId": "<client_id>",
"TenantId": "<tenant_id>",
"Audience": "api://<client_id>"
},
... and the controller looks like this:
[Authorize]
[Route("api/[controller]")]
public class MyApiController : Controller
{
[HttpGet]
public async Task<string> Get()
{
return "Hello world!";
}
}
The underlying cause of the 500 internal error
is that this exception is thrown: IDW10201: Neither scope or roles claim was found in the bearer token.
exception.
UPDATE:
(Please see the answer below for even more details).
This video on "Implementing Authorization in your Applications with Microsoft identity platform - june 2020" suggests that the missing piece is this flag JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
which need to be set in startup.cs
- e.g:
public void ConfigureServices(IServiceCollection services)
{
// By default, the claims mapping will map clain names in the old format to accommodate older SAML applications.
//'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' instead of 'roles'
// This flag ensures that the ClaimsIdentity claims collection will be build from the claims in the token
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
[...more code...]
Scope is a mechanism in OAuth 2.0 to limit an application's access to a user's account. An application can request one or more scopes, this information is then presented to the user in the consent screen, and the access token issued to the application will be limited to the scopes granted.
Bearer token. A particular type of access token, with the property that anyone can use the token. In other words, a client doesn't need a cryptographic key or other secret to use a bearer token. For that reason, bearer tokens should only be used over a HTTPS, and should have relatively short expiration times.
Scopes enable you to group a set of REST resources, methods, or both, and SOAP operations for an API. A scope consists of a name, description, and zero or more resources, methods, or operations. An API can have zero or more scopes. You can define a set of policies and configure its properties for each individual scope.
This might help if you are planning on not using build in scopes or roles. You can enable "access-control list" authentication using my example for Azure B2C below. Here are some links to the official documentation.
https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis#user-content-web-apis-called-by-daemon-apps-using-client-credential-flow
https://docs.microsoft.com/en-us/dotnet/api/microsoft.identity.web.microsoftidentityoptions.allowwebapitobeauthorizedbyacl?view=azure-dotnet-preview
Add the following to your AD configuartion:
"AllowWebApiToBeAuthorizedByACL": true
Example:
"AzureAdB2C": {
"Instance": "https://xxx.b2clogin.com/",
"ClientId": "xxxx",
"Domain": "xxx.onmicrosoft.com",
"SignUpSignInPolicyId": "xxx",
"AllowWebApiToBeAuthorizedByACL": true
},
For what ACL/Access-control list means: ACL: https://en.wikipedia.org/wiki/Access-control_list
When I received this error, "IDW10202", it was because of this line of code in the Controller.
HttpContext.ValidateAppRole("MyAppRole");
(This was the only result returned by Google, so placing this comment here for anyone's benefit. Apologies if a bit off topic.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With