IDW10201:在不记名令牌中找不到范围或角色声明 [英] IDW10201: Neither scope or roles claim was found in the bearer token

查看:21
本文介绍了IDW10201:在不记名令牌中找不到范围或角色声明的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个这样的 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆