从 Net Core WebApi 后端的前端验证 Azure Bearer Token

Validate Azure Bearer Token from Frontend on Net Core WebApi Backend

目标:使用 Microsoft 登录对话框在前端登录 Azure AD 用户。将令牌附加到后端请求。在后端验证令牌以确保只有授权用户才能访问代码。棘手的部分:手动验证,因为它不是唯一的身份验证。

我已经成功登录并发送了令牌,但在验证时出现如下错误:IDX10511: Signature validation failed. Keys tried: ...

这是我目前的情况:

app.module.ts

@NgModule({
  declarations: [
    AppComponent,
    RestrictedPageComponent
  ],
  imports: [
    HttpClientModule,
    MsalModule.forRoot({
      auth: {
        clientId: '<CLIENT ID>',
      }
    }, {
      consentScopes: [
        'user.read',
        'openid',
        'profile',
      ],
      protectedResourceMap: [
        ['https://localhost:44323/v1/login', ['user.read']], // frontend
        ['https://localhost:5001/api/Login', ['user.read']] // backend
      ]
    }),
    BrowserModule,
    AppRoutingModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

login.component.ts

  login() {
    const loginRequest = { scopes: ['https://graph.microsoft.com/User.ReadWrite'] };
    this.authService.loginPopup(loginRequest);
  }

这似乎工作得很好。我可以通过 Microsoft 登录登录,MsalInterceptor 将承载令牌添加到后端请求 header。

在后端,我现在只想验证令牌是否有效以及用户是否已正确验证。

private JwtSecurityToken Validate(string token)
{
    var stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
    // also tried "https://login.microsoftonline.com/<TENANT ID>/v2.0/.well-known/openid-configuration"

    var openIdConnectConfigurationRetriever = new OpenIdConnectConfigurationRetriever();
    var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, openIdConnectConfigurationRetriever);

    var config = configManager.GetConfigurationAsync().Result;

    var validationParameters = new TokenValidationParameters
    {
        IssuerSigningKeys = config.SigningKeys,

        // just for now
        ValidateAudience = false,
        ValidateIssuer = false,
        ValidateLifetime = false
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    IdentityModelEventSource.ShowPII = true;

    token = token.Replace("Bearer ", string.Empty); // weird - the token starts with "Bearer " and is not valid like this
    var result = tokenHandler.ValidateToken(token, validationParameters, out var jwt);

    return jwt as JwtSecurityToken;
}

tokenHandler.ValidateToken(... 的调用中,我总是得到像 IDX10511: Signature validation failed. Keys tried: ... 这样的错误。 我不确定我是否正确理解和使用了整个概念。 我对 Azure Bearer 令牌的用法和验证了解得越多,我就越困惑。

我可以在 http://jwt.io 上解析令牌,但签名始终无效。

后端可以在不传递任何共享密钥或客户端 ID 的情况下验证令牌吗? 这是开始的正确方法吗?

编辑:我不确定我是否使用了正确的端点来调用前端和后端以及不同端点扮演的角色(例如:有或没有租户 ID)。如果有人能解释一下就太好了。

BR 马蒂亚斯

看起来您的前端正在为 Microsoft Graph API 获取访问令牌。 该令牌仅适用于 MS Graph API,不适用于您的 API。 这些 Graph API 令牌在构建方式上也很特殊,您不应尝试验证它们。

相反,在您的前端,您需要为 API 指定范围。

      consentScopes: [
        'your-api-client-id-or-app-id-uri/user_impersonation',
        'openid',
        'profile',
      ],
      protectedResourceMap: [
        ['https://localhost:44323/v1/login', ['user.read']], // frontend
        ['https://localhost:5001/api/Login', ['your-api-client-id-or-app-id-uri/user_impersonation']] // backend
      ]

为此,您需要转到 Azure AD 中 API 的应用程序注册,转到公开 API 选项卡,然后在其中添加一个范围。 然后获取完整的范围 ID(其中包括您的 API 客户端 ID 或应用程序 ID URI + 范围 ID),并在获取令牌时将其用作范围。 然后,Azure AD 应该会为您提供一个用于 API.

的令牌

如果您使用 ASP.NET Core,验证 Azure AD 令牌所需的最低配置是:

// In ConfigureServices
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.Authority = "https://login.microsoftonline.com/your-tenant-id";
        o.Audience = "your-app-client-id";
    });

// In Configure (between UseRouting and UseEndpoints)
app.UseAuthentication();
app.UseAuthorization();

现在,在某些情况下,如果您的 API 配置为在 Azure AD 中接收 v1 令牌,则该令牌可能包含 API 客户端 ID 或应用程序 ID URI。 在这种情况下,您可以配置多个有效受众:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.Authority = "https://login.microsoftonline.com/your-tenant-id";
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidAudiences = new[]
            {
                "your-api-client-id",
                "your-api-app-id-uri"
            }
        };
    })