如何将 OpenID 身份验证从 Blazor WebAssembly 传递到 .NET Core WebApi 后端,两者都使用 Cognito 作为 OpenID 提供程序?

How do I pass OpenID authentication from Blazor WebAssembly to a .NET Core WebApi backend, both using Cognito as the OpenID provider?

技术目标:

后端:

Startup.cs:配置服务

RegionEndpoint region = Configuration.GetAWSOptions().Region;
string CognitoMetadataAddress = $"https://cognito-idp.{region.SystemName}.amazonaws.com/{AppConfig.CognitoPoolId}/.well-known/openid-configuration";

//
// Ref: https://criticalhittech.com/2019/02/19/asp-net-core-with-aws-lambda-and-cognito/
//
services.Configure<OpenIdConnectOptions>(options =>
{
    options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
    options.ClientId             = AppConfig.CognitoClientId;
    options.MetadataAddress      = CognitoMetadataAddress;
    options.ResponseType         = OpenIdConnectResponseType.Code;
    options.SaveTokens           = true;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true
    };
});
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ClientId        = AppConfig.CognitoClientId;
    options.MetadataAddress = CognitoMetadataAddress;
    options.ResponseType    = OpenIdConnectResponseType.Code;
    options.SaveTokens      = true;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true
    };
    options.Events = new OpenIdConnectEvents()
    {
        OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
        OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut,
        OnAuthenticationFailed = OnAuthenticationFailed,
        OnUserInformationReceived = OnUserInformationReceived
    };
});

客户端:

Program.cs:主要

string CognitoPoolId = "ca-central-1_<REMOVED>";
string region = CognitoPoolId.Substring(0, CognitoPoolId.IndexOf('_', StringComparison.InvariantCultureIgnoreCase));
string CognitoAuthority = $"https://cognito-idp.{region}.amazonaws.com/{CognitoPoolId}";
string CognitoMetadataAddress = $"https://cognito-idp.{region}.amazonaws.com/{CognitoPoolId}/.well-known/openid-configuration";

builder.Services.AddOidcAuthentication(options =>
{
    options.ProviderOptions.Authority = CognitoAuthority;
    options.ProviderOptions.MetadataUrl = CognitoMetadataAddress;
    options.ProviderOptions.ClientId = "<REMOVED>";
    options.ProviderOptions.RedirectUri = $"{builder.HostEnvironment.BaseAddress.TrimEnd('/')}/authentication/login-callback";
    options.ProviderOptions.ResponseType = "code";

});

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

问题 在我的 Blazor 应用程序中,我想调用我的后端 API(这需要身份验证)并使用 Blazor 应用程序已有的授权,因为客户端和后端都使用相同的 Cognito 用户池。 即

  1. 加载 Blazor 应用程序
  2. 完成登录
  3. 对我的后端进行 Http 调用 <-- 使用来自 #2
  4. 的登录会话
            HttpResponseMessage response = await Http.SendAsync(requestMessage);

如何配置 HttpClient 实例以发送 Blazor 应用通过 Cognito 身份验证获得的授权,以便它可以调用受保护的 Apis?

在客户端项目 Program.cs 中,您可以按如下方式添加一个 HttpClient,它将向您的 http 请求添加必要的令牌。然后可以根据需要将其注入代码以进行 HTTP 调用。

builder.Services.AddHttpClient("UniqueClientNameHere", client => client.BaseAddress = serverBaseAddress)
                .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("UniqueClientNameHere"));

例如在名为 Foobar.razor:

的 .razor 文件中
@page "/foobar"
@inject HttpClient Http

<h1>Hello Foobar</h1>

@code{
    protected override async Task OnInitializedAsync()
    {
        HttpResponseMessage response = await Http.SendAsync(requestMessage);
    }
}

呃,当然简单了...

@inject HttpClient Http
@inject IAccessTokenProvider TokenProvider
...
HttpRequestMessage requestMessage = new HttpRequestMessage()
{
   ...
};
AccessTokenResult result = await TokenProvider.RequestAccessToken();
if (result.TryGetToken(out AccessToken token))
{
    requestMessage.Headers.Authorization =
        new AuthenticationHeaderValue("Bearer", token.Value);
}
HttpResponseMessage response = await Http.SendAsync(requestMessage);

编辑:对于阅读本文的任何人,请参阅 以及如果您打算在 Cognito 中使用身份池,则需要 id_token 而不是 access_token用户对特定 IAM 角色的范围访问权限。