ASP.NET Core のWebサイトに組み込みのOpenID ConnectスキームでAmazon Cognito User PoolsのログインUIを統合する

Amazon Cognito User Poolsのアプリ統合で、OpenID ConnectっぽいエンドポイントとログインUIが利用できる。 ASP.NET Core Authentication のOpenID Connectスキームは、ASP.NET Core メタパッケージに含まれているビルトインの機能。 Webサイトでajaxを使ったAPIアクセスではなくでブラウジングコンテキストでUser PoolsのIDにログインする。

services
  .AddAuthentication(options =>
  {
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  })
  .AddCookie()
  .AddOpenIdConnect(options =>
  {
    options.ClientId = "client id";
    options.ClientSecret = "client secret";
    options.MetadataAddress = "https://cognito-idp.{Region}.amazonaws.com/{Pool ID}/.well-known/openid-configuration";
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.ResponseType = "code";

    // Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectPostConfigureOptions
    options.Backchannel = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
    options.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OpenIdConnect handler");
    options.Backchannel.Timeout = options.BackchannelTimeout;
    options.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB

    var byteArray = System.Text.Encoding.ASCII.GetBytes(options.ClientId + ":" + options.ClientSecret);
    options.Backchannel.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

    options.ProtocolValidator.RequireNonce = false;
    options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new AmazonUserPools_OpenIdConnectConfigurationRetriever(),
         new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata });
    
    options.Events.OnRedirectToIdentityProviderForSignOut = (context) =>
    {
      context.ProtocolMessage.SetParameter("logout_uri", context.ProtocolMessage.PostLogoutRedirectUri);
      context.ProtocolMessage.SetParameter("post_logout_redirect_uri", null);
      context.ProtocolMessage.SetParameter("client_id", context.Options.ClientId);
      return Task.CompletedTask;
    };
  });
public sealed class AmazonUserPools_OpenIdConnectConfigurationRetriever : IConfigurationRetriever<OpenIdConnectConfiguration>
{
  Task<OpenIdConnectConfiguration> IConfigurationRetriever<OpenIdConnectConfiguration>.GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
  {
    return GetAsync(address, retriever, cancel);
  }
  static async Task<OpenIdConnectConfiguration> GetAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
  {
    var configuration = await OpenIdConnectConfigurationRetriever.GetAsync(address, retriever, cancel);
    var app_url = "https://<domain_prefix>.auth.<region>.amazoncognito.com";
    configuration.TokenEndpoint = app_url = "/oauth2/token";
    configuration.AuthorizationEndpoint = app_url + "/login";
    configuration.EndSessionEndpoint = app_url +  "/logout";
    return configuration;
  }
}

OpenID Provider Configuration

.well-known/openid-configuration で得られる情報が足りないので、IConfigurationRetrieverの実装で取得したOpenIdConnectConfigurationに不足するプロパティの値を設定した。

Backchannel HttpClient

エンドポイントへのアクセスはベーシック認証。Authorization ヘッダーでClient Secretを渡す必要がある。OpenIDConnectionOptionsのBackchannelのHttpClientで設定する。Backchannelは既定ではOpenIdConnectPostConfigureOptionsで構築される。既定のHttpClientにヘッダーを追加することはできなかったので、同じように作ってヘッダーを追加したHttpClientのインスタンスをOptionsに設定した。

おまけ

Razor PagesのPageModelではメソッドレベルのAuthorizeAttributeは効かない。