IDW10201:在不记名令牌中找不到范围或角色声明 [英] IDW10201: Neither scope or roles claim was found in the bearer token
问题描述
我有一个这样的 ASP.NET Core 3.1 项目:在 WPF 桌面应用程序中使用 Microsoft Identity Platform 登录用户并调用 ASP.NET Core Web API.
我使用的是 Identity web
1.0 版和Azure AD,单租户应用程序.
我已经编辑了添加 appRoles
的清单,因为我只请求应用程序令牌,而不是用户令牌:
[... 更多 json ...]appId":",isEnabled":真,lang":空,起源":应用",值":access_as_application";}],oauth2AllowUrlPathMatching":假,[...更多json ...]
我还启用了 idtyp
访问令牌声明,以指定这是一个应用程序令牌.:
[... 更多 json ...]可选声明":{idToken":[],访问令牌":[{名称":idtyp",来源":空,基本":错误,附加属性":[]}],saml2Token":[][...更多json ...]
以下请求是由 Postman 提出的.请注意将 /.default
与范围一起使用,这在与 客户端凭据授予流程.
POST/{tenant_id}/oauth2/v2.0/token HTTP/1.1主机:login.microsoftonline.com内容类型:应用程序/x-www-form-urlencoded范围=api%3A%2F%2{client_id}%2F.default&client_id={client_id}&grant_type=client_credentials&client_secret={secret_key}
请求返回一个access_token
,可以用jwt.ms查看,如下所示,出于安全原因,实际数据已被占位符替换.:
{典型":JWT",alg":RS256",x5t":[...]",孩子":[...]"}.{aud":api://",iss":https://sts.windows.net//",iat":1601803439,nbf":1601803439,exp":1601807339,aio":[...]==",appid":",appidacr":1",idp":https://sts.windows.net//",idtyp":应用程序",oid":",tid":<guid>",uti":[...]",版本":1.0"}
我注意到上面的标记不包括 scp
.这似乎是正确的,因为这是一个应用程序令牌而不是用户令牌.相反,它包含适合应用程序令牌的角色".
access_token
现在可以在 Postman Get 中用作承载:
GET/api/myapi主机:https://localhost:5001授权:承载 {access_token}
对此请求的响应是500 internal error
.IE.有什么不对.access_token
看起来像一个正确的应用程序令牌,所以错误似乎是在 ASP.NET Core 3.1 控制器端.
ASP.NET Core 3.1.托管自定义 API 的项目有一个 startup.cs
,其中包含以下代码:
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);//这是为了突出异常的起源而添加的.services.Configure(JwtBearerDefaults.AuthenticationScheme, options =>{var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;options.Events.OnTokenValidated = 异步上下文 =>{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)){//这是异常的来源:throw new UnauthorizedAccessException(在不记名令牌中找不到范围或角色声明.");}};});
项目的 appsettings.json
包括:
AzureAD":{实例":https://login.microsoftonline.com/",域":mydomain.onmicrosoft.com",ClientId":",TenantId":",受众":api://"},
... 控制器看起来像这样:
[授权][路由(api/[控制器]")]公共类 MyApiController :控制器{[HttpGet]公共异步任务得到(){return "Hello world!";}}
500 内部错误
的根本原因是抛出了这个异常:IDW10201:在承载令牌中找不到范围或角色声明.
异常.>
更新:
(有关详细信息,请参阅下面的答案).
此视频关于使用 Microsoft 标识平台在您的应用程序中实施授权 - 2020 年 6 月"表明缺少的部分是这个标志 JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
需要在 startup.cs
中设置 - 例如:
public void ConfigureServices(IServiceCollection services){//默认情况下,声明映射将以旧格式映射clain 名称以适应旧的SAML 应用程序.//'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' 而不是 'roles'//此标志确保 ClaimsIdentity 声明集合将从令牌中的声明构建JwtSecurityTokenHandler.DefaultMapInboundClaims = false;[...更多代码...]
视频实施使用 Microsoft 标识平台在您的应用程序中进行授权 - 2020 年 6 月"概述缺少的部分是这个标志 JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
需要在 startup.cs
中设置 - 例如:
public void ConfigureServices(IServiceCollection services){services.AddMicrosoftIdentityWebApiAuthentication(Configuration);//默认情况下,声明映射将以旧格式映射声明名称以适应旧的 SAML 应用程序.//'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' 而不是 'roles'//此标志确保 ClaimsIdentity 声明集合将从令牌中的声明构建JwtSecurityTokenHandler.DefaultMapInboundClaims = false;//注意这部分在视频中是不同的,//但是在这种情况下,以下内容似乎是//设置 RoleClaimType 的正确方法:services.Configure(JwtBearerDefaults.AuthenticationScheme, options =>{//应用程序角色可用的 Jwt 令牌中的声明.options.TokenValidationParameters.RoleClaimType = "roles";});[...更多代码...]}
备选方案 1
也可以像这样在startup.cs
中为整个应用设置授权:
services.AddControllers(options =>{var policy = new AuthorizationPolicyBuilder().RequireClaim("roles", "access_as_application").建造();options.Filters.Add(new AuthorizeFilter(policy));});
备选方案 2
也可以使用这样的策略:
services.AddAuthorization(config =>{config.AddPolicy(角色",策略=>policy.RequireClaim("roles", "access_as_application"));});
现在这个策略可以用于这样的控制器请求:
[HttpGet][授权(政策=角色")]公共异步任务得到(){return "Hello world!";}
文档中的更多内容:基于策略的角色检查.
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...]
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.
这篇关于IDW10201:在不记名令牌中找不到范围或角色声明的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!