3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

FlutterとAWSで始めるサービス開発 (8)Cognitoの認証情報を使ってAPIを呼び出す

はじめに

(5)AWS Cognitoでログイン」や、「(7)AWS Cognito Googleでログイン」で、Cognitoを使ったログイン処理を一通り実装完了しました。今回はそれら認証情報がないと呼び出せないAPIを実装していきたいと思います。前回まででCognito ユーザープールからID Tokenを取得するところまではできています。APIを呼び出すために、ID Tokenを要求し、それを検証してから実行するAPIを作っていきます。つまり、ログインした場合だけサービスのAPIを呼び出せるというモデルを実現するための構成です。認証した上でさらに特定の権限を持っているユーザーだけが利用できるといったAPIも考えられますが、今回はそこまでは踏みこみません。

参考文献

APIの作成

早速APIを作成していきます。AWSマネージメントコンソールからLambdaを開き、関数の作成を押下します。
lambda1.jpg

まず、設計図の使用を選択し、httpでフィルタします。microservice-http-endpoint-pythonを選択します。python版の選択理由は単に筆者の職場では、Lambdaのアプリをpythonで書いていて慣れているからです。
lambda2.jpg

関数名には任意の名前を入れてください。実行ロールは、基本的なLambdaアクセス権限で新しいロールを作成を選択します。
lambda3.jpg

API Gaeway トリガーではAPICreate an APIAPI TypeRESTセキュリティオープンを選択します。API名は任意に決めてください。入力が終わったら追加ボタンを押下します。
lambda4.jpg

Lambda関数ができるので、関数のコードを書き換えます。
lambda5.jpg

以下が、変更後のコードです。一部元のソースを流用していますが、ほぼ書き換えてしまっています。本実装では、呼び出し時に渡されたeventの一部(event['requestContext']['authorizer']['claims'])を返却することだけをしています。event['requestContext']['authorizer']['claims']に関しては後ほど解説したいとおもいます。

import boto3
import json

print('Loading function')


def respond(err, res=None):
    return {
        'statusCode': '400' if err else '200',
        'body': err.message if err else json.dumps(res),
        'headers': {
            'Content-Type': 'application/json',
        },
    }


def lambda_handler(event, context):

    response =  {
            "claims" : event['requestContext']['authorizer']['claims']
        }

    return respond(None, response)

以上で、APIの骨格が完成しましたが、次に、APIにCognitoとの認証連携を設定していきます。マネージメントコンソールでAPI Gatewayを開くと、先ほど作成したAPIが表示されるので選択します。

lambda6.jpg

オーソライザーを選択し、新しいオーソライザーの作成を押下します。名前には任意の名前を入力し、タイプCognitoを選択します。Cognitoユーザープールでは、作成してあるプールを選択し、トークンのソースAuthorizationを入力し、作成ボタンを押下します。
apigw0.jpg
リソースを選択し、作成したAPIのANYを選択し、メソッドリクエストを選択します。
apigw1.jpg

認可Cognitoユーザープールオーソライザーで先ほど作成したオーソライザーを選択します。
apigw2.jpg

アクションからAPIのデプロイを選択し、デプロイされるステージdefaultを選択しデプロイボタンを押下します。
apigw3.jpg

apigw4.jpg

以上でサーバー側の設定が完了です。

APIの呼び出し

ここまでで作ったAPIをFlutterのアプリケーションから呼び出します。
(5)AWS Cognitoでログインで作成したトップ画面を修正します。
まず、画面にAPIの呼び出し結果をテキストで表示する機能を追加します。非同期処理になるので、FutureBuilder<>を利用します。FutureにAPIを呼び出し、レスポンスを文字列で返却する関数である_invokeApi()を設定します。builderではデータを受け取ったら、API呼び出し結果をTextで返却、データを受け取らない間は、CircularProgressIndicator()で処理中を表示するようにしています。
引き続き、_invokeApi()の中身を確認します。先ほど作成したAPIの呼び出しコードです。Autorizationヘッダに、(5)AWS Cognitoでログイン(7)AWS Cognito Googleでログインで取得した、CognitoUserSessionインスタンスの、JWT形式IDトークンを設定しGET呼び出しを行っています。また、APIの呼び出し結果のResponse.bodyを戻り値としています。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('トップページ'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('ログイン成功'),
            Divider(color: Colors.black),
            FutureBuilder<String>(
                future: _invokeApi(session),
                builder: (context, AsyncSnapshot<String> snapshot) {
                  if (snapshot.hasData) {
                    return Text(snapshot.data);
                  } else {
                    return CircularProgressIndicator();
                  }
                })
          ],
        ),
      ),
    );
  }

  Future<String> _invokeApi(CognitoUserSession session) async {
    String url =
        "https://2wmzck1189.execute-api.ap-northeast-1.amazonaws.com/default/testCognito";
    final response = await http.get(url,
        headers: {'Authorization': session.getIdToken().getJwtToken()});
    if (response.statusCode != 200) {
      throw Exception("Received bad status code from API:" +
          response.statusCode.toString() +
          "; body: " +
          response.body);
    }

    return response.body;
  }
}
``

実際に本アプリを実行すると以下のように画面にレスポンスが表示されます。

![app1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273926/5bae9119-23dc-8c2f-e282-6d89cbb36ef3.jpeg)


以上で、最低限のアプリケーションは完成です。

# API側での処理
API GatewayのオーソライザーでCognitoID Tokenが検証できた場合、Lambda側には検証結果の情報として、Cognitoユーザーの情報がわたってきます。
前述した``event['requestContext']['authorizer']['claims']``にID Tokenの検証結果としてログインしているユーザーの情報が入ります。以下が具体的な、cliamsの中身になります。__sub__Cognitoのユーザープール内でユーザーを一意に識別するIDがわたってくるので、アプリケーションはそのキーを使って、ユーザーごとの処理を実装していくことが可能です。

``` json
"claims": {
  "at_hash": "jC8Q3e2kh1LmzloesriHHw",
  "sub": "bdec0872-9384-453f-99f9-c8c50dee23db",
  "aud": "***********************",
  "cognito:groups": "ap-northeast-1_*********_Google",
  "identities": "{"dateCreated":"1594465378058","userId":"100299533705733233603","providerName":"Google","providerType":"Google","issuer":null,"primary":"true"}",
  "token_use": "id",
  "auth_time": "1595514490",
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_*********",
  "cognito:username": "google_100299533705733233603",
  "exp": "Thu Jul 23 15:28:10 UTC 2020",
  "iat": "Thu Jul 23 14:28:10 UTC 2020"
}

まとめ

Cognitoでログインして取得したIDトークンを利用してAPIの呼び出しを実施するところまでできました。これでサービス開発の最低限の流れはできたと思います。本シリーズはいったんここまでで終わりたいと思います。ここまで実装したコードを一通りリファクタリングしたり、エラー処理を拡充して、もう少し人に見せられそうなレベルに持っていけたらソースコードを公開しようかなと思います。また、あまり技術的なことは深く突っ込まず、どう設定すれば動くのかに比重をおいて進めてきましたが、もう少し技術面を整理してその辺の情報も公開できれば名智考えています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?