IDW10201: Neither scope or roles claim was found in the bearer token
Solution 1
The video "Implementing Authorization in your Applications with Microsoft identity platform - june 2020" outlines 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)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
// By default, the claims mapping will map claim 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;
// Notice that this part is different in the video,
// however in this context the following seems to be
// the correct way of setting the RoleClaimType:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
// The claim in the Jwt token where App roles are available.
options.TokenValidationParameters.RoleClaimType = "roles";
});
[... more code ...]
}
Alternative 1
It is also possible to set authorization for the whole app like this in startup.cs
:
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireClaim("roles", "access_as_application")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Alternative 2
It is also possible to use a policy like this:
services.AddAuthorization(config =>
{
config.AddPolicy("Role", policy =>
policy.RequireClaim("roles", "access_as_application"));
});
Now this policy can be used on a controller request like this:
[HttpGet]
[Authorize(Policy = "Role")]
public async Task<string> Get()
{
return "Hello world!";
}
More in the documentation: Policy based role checks.
Solution 2
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.
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
Solution 3
Just add DefaultMapInboundClaims to your API service config
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
}
Solution 4
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.)
1iveowl
BY DAY: Microsoft Principal Engineer, Azure FastTrack for ISVs and Start-ups. BY WEEKEND: Programmer: Azure, .NET 5, C#, Reactive Extensions and whatever I can get my hands on and the time for. GitHub LinkedIn NuGet Packages
Updated on June 16, 2022Comments
-
1iveowl almost 2 years
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. Theaccess_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 instartup.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...]
-
1iveowl over 3 yearsAre you sure about that. If I change the scope when requesting the token I get this error instead:
"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://{client_id}/access_as_application is not valid.\r\nTrace ID"
-
1iveowl over 3 yearsThis is what the documentation says: Client credentials grant flow and /.default (updated with the correct link)
-
svyat1s over 3 yearshave you properly config your Azure app for your client, incliding adding api permission for your
api://{client_id}/access_as_application
scope ? Because it's strange situation you access_token should contain either scope or role claims and azure isn't issuing scope claim because of .default scope and it seems that you web api app has no permissions/roles in azure and that's why role claims aren't issued too, -
1iveowl over 3 yearsYes. I'm updated the manifest and now I'm actually getting the Roles, but I'm still getting the same exception. Please see the updates in the original post.
-
1iveowl over 3 yearsIf you want to be helpful explain why this doesn't work
[Authorize(Roles = "access_as_application")]
. You can see the details in my answer to the question. -
svyat1s over 3 yearsahh sorry, I have been researching that behaviour in the project and haven't refresh the page, so I've missed your updates
-
svyat1s over 3 yearsit's probably because that your roles isn't actually roles but claims try to add policy like policy.RequireClaim("roles", "access_as_application")), and than use that policy in your controller
-
1iveowl over 3 yearsSry about that, made an assumption I shouldn't have made.
-
1iveowl over 3 yearsDidn't work. Did this:
services.AddControllers(options => { var policy = new AuthorizationPolicyBuilder() .RequireClaim("roles", "access_as_application") .Build(); options.Filters.Add(new AuthorizeFilter(policy)); })
-
1iveowl over 3 yearsWell it sort of did work, in the sense that it set the requirement of the
access_as_application
globally for the whole app. However, it still fails if I put the[Authorize(Roles = "access_as_application")]
attribute on the controller itself. -
svyat1s over 3 yearshmm strange for me it works
services.AddAuthorization(config => { config.AddPolicy("Role", policy => policy.RequireClaim("roles", "access_as_application")); });
but your solution is good too -
1iveowl over 3 yearsAgree, your code works for the whole app, just like mine. I think it's doing the same thing. Are you able to use the attribute on the controller only:
[Authorize(Roles = "access_as_application")]
, cause I'm not. -
svyat1s over 3 yearsin the controller you should specify not a Roles but the policy you have created
[Authorize(Policy = "Role")]
-
juagicre over 3 yearsAvoid using links to other answers, instead you can summarize and add the steps, info or whatever it's needed to answer the question.
-
Endre86 over 3 years@juagicre I do not think linking to official documentation is the same as "linking to other answers". Also, I think it is important to read the official documentatio, specially when changing the default behavior of your applications security. The documentation has a much greater chance to be updated before this SO answer. Lastly, I did write how to enable what I suggested in the post.
-
VJPPaz over 2 yearsThis solves our problem. Thank you!
-
Birtija over 2 yearsThis solved my problem!
-
karoberts about 2 yearsWould have been nice if the error message mentioned this!