AWS Lambda(C#)でCognitoの認証情報を取得する方法

2020.12.15

はじめに

CognitoをオーソライザーにしたAPI Gatewayの認証情報をLambdaの中で取得する方法を調べました。言語はC#で.NET Coreのバージョンは3.1を使っています。

手順

  1. Cognitoのユーザプールとユーザーを作成してください。ユーザーとアプリクライアントを1件登録しておいてください。
  2. 下のソースコードでLambdaファンクションを作成してください。
  3. API Gatewayを使ってAPIを作成します。作成したLambdaファンクションを呼び出します。「プロキシ統合の使用」にチェックを入れてください。
  4. API Gatewayのオーソライザーは作成したCognitoのユーザープールを設定します。トークンのソースは今回は「Authorization」にしました。
  5. 認証情報を取得できているか確認します。私はテストアプリを作りました。

参照しているパッケージ

Lambdaのパッケージは以下を参照しています。

  • Amazon.Lambda.APIGatewayEvents 2.4.0
  • Amazon.Lambda.Core 1.2.0
  • Amazon.Lambda.Serialization.SystemTextJson 2.1.0
  • AWSSDK.APIGateway 3.5.2.15
  • AWSSDK.Lambda 3.5.5.3

Lambdaのソースコード

以下、Lambdaのソースコードになります。内容はHTTPヘッダーにあるIDトークンを解析して、Cognitoに登録したメールアドレスを返しています。

using Amazon.Lambda.Core;
using System;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using Amazon.Lambda.APIGatewayEvents;
using System.Text;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace CognitoTest
{
    public class Function
    {
        public APIGatewayProxyResponse FunctionHandler(Request request, ILambdaContext context)
        {
            var idToken = request.Headers.GetProperty("Authorization").GetString();
            var section = idToken.Split('.')[1];
            var padded = section.PadRight(section.Length + (section.Length * 3) % 4, '=');
            var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(padded));

            string email = "";
            using (JsonDocument document = JsonDocument.Parse(decoded))
            {
                JsonElement root = document.RootElement;
                email = root.GetProperty("email").GetString();
            }

            return new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = email
            };
        }
    }

    public class Request
    {
        [JsonPropertyName("body")]
        public string Body { get; set; }

        [JsonPropertyName("httpMethod")]
        public string HttpMethod { get; set; }

        [JsonPropertyName("headers")]
        public JsonElement Headers { get; set; }

        [JsonPropertyName("requestContext")]
        public JsonElement RequestContext { get; set; }
    }
}

追記

後で気づいたのですが、メールアドレスやCognitoユーザーIDの取得であれば、requestContextというプロパティに入っていました。以下のようにしてメールアドレスを取得できます。HTTPヘッダーからIDトークンを取得するよりも、こちらの方が簡単でいいですね。

var authorizer = request.RequestContext.GetProperty("authorizer");
var claims = authorizer.GetProperty("claims");
var mail = claims.GetProperty("email").GetString();

確認する

私はUWPで簡単なテストアプリを作って確認しました。画面側はボタン1つだけの画面なので割愛します。やっていることはCognitoへのログインとAPIの呼び出しです。Cognitoにログインした際に取得したIDトークンをAPI呼び出し時にHTTPヘッダーに入れています。パッケージは以下をダウンロードしました。

  • Amazon.Extensions.CognitoAuthentication.2.0.0
  • AWSSDK.CognitoIdentity.3.5.1.9
  • AWSSDK.CognitoIdentityProvider.3.5.1.7
using Amazon;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
using System;
using System.Diagnostics;
using System.Net.Http;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace CongnitoTest
{
    public sealed partial class MainPage : Page
    {
        // お使いの環境の情報を設定してください。
        private const string POOL_ID = "プールID";
        private const string CLIENT_ID = "クライアントID";
        private const string USERNAME = "ユーザー名";
        private const string PASSWORD = "パスワード";
        private const string URI = "URI";

        public MainPage()
        {
            this.InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // 認証
            var provider = new AmazonCognitoIdentityProviderClient(null, RegionEndpoint.APNortheast1);
            CognitoUserPool userPool = new CognitoUserPool(
                POOL_ID,
                CLIENT_ID,
                provider
            );

            CognitoUser user = new CognitoUser(
                USERNAME,
                CLIENT_ID,
                userPool,
                provider
            );

            AuthFlowResponse response = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
            {
                Password = PASSWORD
            }).ConfigureAwait(false);

            // API
            var httpRequest = new HttpRequestMessage(HttpMethod.Post, new Uri(URI));
            string idToken = response.AuthenticationResult.IdToken;
            httpRequest.Headers.Add("Authorization", idToken);
            using (var client = new HttpClient())
            {
                var res = await client.SendAsync(httpRequest);
                if (res.StatusCode.Equals(System.Net.HttpStatusCode.OK))
                {
                    Debug.WriteLine("success:" + await res.Content.ReadAsStringAsync());
                }
                else
                {
                    Debug.WriteLine("failure");
                }
            }
        }
    }
}