UnityでCognito UserPoolsから得たトークンでリクエストし、API Gatewayでsubを受け取る

前回の記事の続きです。
今回はUserPoolsにサインアップして、AWS側で認証したユーザーを確認するまでを行います。

API GatewayとCognito UserPoolsを連携する

事前に、Cognito UserPoolsを作成します。 前回の記事でCognito UserPoolsのUserPoolを作成しているので、追加で以下の操作を行います。

以下の操作のドキュメントはこちらを参照してください。

API GatewayでResourceとMethodを作成

リソース名としては良くないですが、今回はGET /authorizeを作成しました。

API Gateway Authorizerを作成

マネジメントコンソールのAPIs > Authorizers > Create New Authorizer から、UserPoolsを指定してAuthorizerを作成しました。
Authorization Headerを見るので、Token SourceにはAuthorizationと記入します。

Method RequestのAuthorizationにAuthorizerを指定

以下の点に気をつけて指定します。

  • Authorizationには、先ほど作成したAPI Gateway Authorizerを指定する
  • OAuth ScopeはNONEのままにしておく

Request Validatorはデフォルトのままでも良いのですが、ValidationすることでAuthorization HeaderがないリクエストではLambdaを起動しない挙動になり、セキュリティの実装とコスト削減を手軽に実現できます。
この設定をする場合は、Method RequestのHTTP Request HeadersにAuthorizationを追加し、Requiredに設定します。

Integration RequestでLambda Proxyを設定

このあとUserPoolsのUser NameをsubとしてLambda Functionのevent objectで受け取れることを確認するので、Lambda Proxyを設定します。

事前に以下のようなLambda Functionを作成します。詳細は割愛します。

import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(event)
    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'success'}, ensure_ascii=False),
        'headers': {'Content-Type': 'application/json'}
    }

ここまでやったらStageにDeployします。

DeployしたAPIをUserPoolsのApp IntegrationでResource Serverに設定する

ドキュメントはこちらです。 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-create-cognito-user-pool.html

  • Nameは適当に入力します
  • Identifierは、StageにDeployされたAPI Gateway APIに付与されるURLを記載します。Custom Domainを設定している場合はそちらを設定します。

Unity側のソースコードを変更する

前回のサンプルアプリを修正します。画面は以下の画像のようにしました。

サインアップとConfirmation Codeのスクリプトは前回の記事と一緒です。

サインイン

サインインも前回とやってることは一緒なのですが、APIの動作確認を楽にするためにIdTokenを自動でGETリクエストのフォームに入力するようにしました。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Amazon;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;

public class Signin : MonoBehaviour {

    public InputField emailField;
    public InputField passwordField;
    public InputField tokenField;
    static string clientId = "MyClientId";
    static string userPoolId = "us-west-2_xxxxxxx";

    void Start () {
    }

    void Update () {
    }

    public void OnClick() {
        Debug.Log("Start Signin");

        try {
            CallAuthenticateTask();
        } catch(Exception ex) {
            Debug.Log(ex);
        }
    }

    private async void CallAuthenticateTask() {
        var context = SynchronizationContext.Current;
        AuthFlowResponse response = await AuthenticateWithSrpAsync();
        context.Post((state) => {
            tokenField.text = response.AuthenticationResult.IdToken;
        }, null);
    }

    private async Task<AuthFlowResponse> AuthenticateWithSrpAsync() {
        var provider = new AmazonCognitoIdentityProviderClient (null, RegionEndpoint.USWest2);
        CognitoUserPool userPool = new CognitoUserPool(
            userPoolId,
            clientId,
            provider
        );
        CognitoUser user = new CognitoUser(
            emailField.text,
            clientId,
            userPool,
            provider
        );

        return await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest(){
                Password = passwordField.text
        }).ConfigureAwait(false);
    }
}

Authorization Header付きGETリクエスト

詳細はUnityのドキュメントを参照してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using UnityEngine.Networking;

public class GetRequest : MonoBehaviour {

    public InputField tokenField;
    // API Gatewayが作成して、Resource Serverにも指定したURL
    static string url = "https://api-gw-published-url/authorize";

    void Start () {
    }

    void Update () {
    }

    public void OnClick () {
        StartCoroutine(GetText());
    }

    IEnumerator GetText() {
        string token = tokenField.text; // UserPoolsから取得したIdToken
        UnityWebRequest www = UnityWebRequest.Get(url);
        www.SetRequestHeader("Authorization", token); // Authorization Headerの付与
        yield return www.SendWebRequest();

        if (www.isNetworkError || www.isHttpError) {
            Debug.Log(www);
        } else {
            Debug.Log(www.downloadHandler.text);
        }
    }
}

動作確認

Lambdaのlogger.info(event)がCloudWatch Logsに出力されています。サインアップしたユーザーの情報が取得できていることがわかります。

{..., 'headers': {..., 'Authorization': 'eyJra...vqA', ..., 'requestContext': {..., 'authorizer': {'claims': {'sub': '0c08e593-f9f8-aaaa-bbbb-ccccccc', 'aud': '', 'email_verified': 'true', 'event_id': '255d265f-3bae-aaaa-bbbb-ccccccc', 'token_use': 'id', 'auth_time': '1523247977', 'iss': 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXXX', 'cognito:username': '0c08e593-f9f8-aaaa-bbbb-ccccccc', ...}}, ...

まとめ

前回の記事に引き続き、Cognito UserPoolsとAPI Gatewayの連携を設定して、UnityアプリからAuthorization Header付きでHTTPリクエストを送り、クラウド側でUserPoolsが発行するUser IDをLambda Functiondで取得しました。