如何在 .NET Core 3.1 WebApi 中将 JWT 令牌交换为 Cognito 身份池中的凭据

How to exchange JWT token for Credentials in Cognito Identity Pool in .NET Core 3.1 WebApi

概览:我正在尝试创建一个针对 Amazon Cognito 进行身份验证的 .Net Core 3.1 WebApi 后端。我想使用 Cognito 提供的 Amazon-hosted sign-in 页面。我想在用户登录后利用 Cognito Identity Pool 为用户提供临时范围内的凭据。我不知道如何交换 Cognito 令牌来创建凭据来调用 AWS 服务。

技术概览

目前有以下两项工作:

在这两种情况下,都允许访问 API,但是在 WebApi 中创建的 AmazonServiceClient 实例被授予与 Lambda 函数关联的权限(这是正确的行为)。

问题

我需要创建 AmazonServiceClients,其凭据与 Cognito 身份池定义的角色相匹配。

为此,我需要交换通过登录 Cognito 用户池提供的令牌以获取身份池中的临时凭据。

我在这个过程中可以找到的几乎所有示例和文档都定义了如何使用 API(不是托管网站 UI)手动登录 Cognito,然后使用 API 响应以创建 CognitoUser,然后使用该用户从身份池获取凭据。

我能找到的最接近(虽然 超级 简短)的文档来自 AWS:https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/cognito-creds-provider.html

// Authenticate user through Facebook
string facebookToken = GetFacebookAuthToken();

// Add Facebook login to credentials. This clears the current AWS credentials
// and retrieves new AWS credentials using the authenticated role.
credentials.AddLogin("graph.facebook.com", facebookAccessToken);

虽然该示例使用 Facebook,但从概念上讲,它对于任何提供商(Facebook、Google、Twitter、OpenId 等)都应该是相同的。

我目前的尝试

我已将 CognitoAWSCredentials 注册为范围服务,因为它是 user-specific,因此只要 API 请求 session 存在,它就应该存在。

RegionEndpoint region = Configuration.GetAWSOptions().Region;
services.AddScoped(_ => new CognitoAWSCredentials(Settings.CognitoIdentityPoolId, region));

我已经创建了一个事件处理程序,它在 OpenIdConnect 事件 'OnTokenValidated' 被触发时被触发。这发生在我登录到 Cognito 托管网站 UI 并被重定向回我的 API.

在这个处理程序中我可以调用:

CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
creds.AddLogin( ... ??? ...);

(注意:因为我在 Startup.ConfigureServices(IServiceCollection services) 方法中设置了所有这些,所以每次身份验证成功时我都会构建一个 IServiceProvider 实例......这可能是效率低下,但我还没有想出另一种方法来访问 ConfigureServices 方法中的范围服务)

所有这些序言都说我找不到允许此测试调用成功的 AddLogin 调用的一组值:

ImmutableCredentials immCreds = creds.GetCredentials();

相关数据结构

在我可以调用 AddLogin 的事件处理程序中,我可以访问:Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext,其中特别包含:

{
    {
        "alg": "RS256",
        "kid": "**************************"
    }. {
        "at_hash": "**************************",
        "sub": "**************************",
        "email_verified": true,
        "iss": "https://cognito-idp.ca-central-1.amazonaws.com/**************************",
        "cognito:username": "**************************",
        "nonce": "**************************",
        "aud": "**************************",
        "event_id": "**************************",
        "token_use": "id",
        "auth_time": 1595260191,
        "exp": 1595263791,
        "iat": 1595260191,
        "email": "**************************"
    }
}

我尝试使用 iss 值作为 AddLogin 中的 providerName,并且 access_tokenid_token 都不起作用。

有谁知道我需要为 AddLogin 使用什么,以便 Cognito 根据来自 Cognito 用户池登录的 JWT 令牌为我创建身份池凭据?

除非我错过了它,否则我还没有看到说明这一点的文档,但即使各种数据结构上的所有 Issuer 字段都包含 'https://',您也需要在将 Issuer 用作AddLogin 通话中的 providerName。呃

CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
string shortIssuer = tokenValidatedContext.SecurityToken.Issuer;
if (shortIssuer.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("https://".Length);
if (shortIssuer.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("http://".Length);
creds.AddLogin(shortIssuer, tokenValidatedContext.TokenEndpointResponse.IdToken);

现在,上面的代码有一个问题,因为 services.BuildServiceProvider(). 部分意味着我修改的凭据对象不是全局的(我认为只是我在这里构建的服务提供商的本地对象),但这是一个不同的问题- 只是注意以防万一有人复制此代码。

            services...<other authentication setup>...
                    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
                        {
                            options.ClientId        = Settings.CognitoClientId;
                            options.MetadataAddress = CognitoMetadataAddress;
                            options.ResponseType    = OpenIdConnectResponseType.Code;
                            options.SaveTokens      = true;
                            options.UsePkce         = true;
                            options.TokenValidationParameters = new TokenValidationParameters()
                            {
                                ValidateIssuer = true,
                                ValidIssuers = new string[] { Settings.CognitoAuthority },
                                ValidateAudience = true,
                                ValidAudiences = new string[] { Settings.CognitoClientId }
                            };
                            options.Events = new OpenIdConnectEvents() {
                                OnTokenValidated = tokenValidatedContext => {
                                    CognitoAWSCredentials creds = services.BuildServiceProvider().GetRequiredService<CognitoAWSCredentials>();
                                    string shortIssuer = tokenValidatedContext.SecurityToken.Issuer;
                                    if (shortIssuer.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("https://".Length);
                                    if (shortIssuer.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) shortIssuer = shortIssuer.Substring("http://".Length);
                                    creds.AddLogin(shortIssuer, tokenValidatedContext.TokenEndpointResponse.IdToken);
                                    return Task.CompletedTask;
                                }
                            };
                        })

(删除了一些代码以专门关注 OpenId Connect 事件和 CognitoAWSCredentials init)