AWS Cognitoを使ってみた

AWS Cognitoを使ってみた:

本記事はOPENLOGI AdventCalendar 10日目の記事です。

どうも今年もネタが思いつかない@small-tetonです。

今回はプライベートでAWS Cognitoを触り始めたのとCognitoの記事はまだ少ないので

触ってみた系の記事で恐縮ですがCognitoを紹介できればと思います。


Cognitoとは

みなさんはCognitoをもう使ってますかね?

私は「なんか認証をいい感じにしてくれるやつ」くらいの認識しかありませんでした。

実際、Cognitoは認証周りの機能をSaasで提供するものです。

似たようなサービスにGoogleのFirebaseやAuth0がありますね。

私はどちらも使ったことはないのですが機能面ではAuth0が最もイケてるという話を聞きますが

CognitoはAWSでサーバーレスを構築したい人にはAPI Gatewayと連携できるのは強みです。

一口にCognitoと言っては詳しくは3つのサービスが提供されています。


User Pool

ユーザーの管理とサインアップ/サインイン、JWTの発行


ID Pool

Federated identityと呼ばれる類いのサービスです。要はAWS IAM以外のシステムで管理されるユーザーに対して一意なIDを発行できるわけですが、そのIDに対して一時的なIAM権限を持つtokenであるAWS STSを発行する機能を持ちます。Cognito User Poolで管理されるユーザーももちろん対象にできます。


Cognito Sync

ID Poolで発行されたIDに対して設定や状態を保存するためのストレージを提供する。データ更新などのイベントに対してSNS push通知が出来たりLambda実行をしたりすることができる。|

料金については詳しくはAWSの説明を見てもらうしかありませんが

感想としてはかなり安く、Cognitoのコストが問題になることは基本的に無さそうです。
https://aws.amazon.com/jp/cognito/pricing/


Amplify

AWSサービスなのでhttpsのAPIを持っているわけですが

Cognitoを使う手段としてAWS CLIやSDKの他に、AmplifyというJavaScriptライブラリがAWSによって公開されています。
https://github.com/aws-amplify/amplify-js

Cognito認証関連以外にも色々と機能を持っていますがフロントエンドをS3、バックエンドをAPI Gateway + LambdaでサーバーレスWEBアプリを構築するケースにおいて

フロントエンド側ではAmplify & Cognitoを使うことで認証周りが大分楽になるでしょう。


準備


Cognito User Pool

Cognitoのページに行くとこのような画面になるので「ユーザープールの管理」へ



スクリーンショット 2018-12-09 21.17.00.png


最初はリストが空なので新規作成に進みます

スクリーンショット 2018-12-09 21.18.32.png

適当な名前を付けます。今回はステップごとに設定していきます。


スクリーンショット 2018-12-09 21.19.29.png


今回は任意のユーザー名を使うものとし、「ユーザー名」「Eメールアドレスおよび電話番号」はデフォルトのままの設定とします。

標準属性についても今回はusernameとパスワードのみを管理するものとし、デフォルトではemailにチェックが付いていますが全て外します。

チェックが付いているとSignup時に入力を求められる他、Signin後に発行されるJWTにもクレーム情報として返ってきます。

クレームというのは「文句を言う」というニュアンスでも使われますが、「要求する」という意味もあり「必須項目として入力を求める」というニュアンスで使われているのかと思います。

ちなみにこのページで設定する項目は後で編集することができません。間違った場合は作り直しになるので注意しましょう。



スクリーンショット 2018-12-09 21.20.49.png


パスワード強度を設定できます。「数字を必要とするs」のケツに謎のsが付いてますね・・・

動かしてみたレベルだと面倒なのでここのチェックを全部外しました。最小長は6文字がミニマムです。



スクリーンショット 2018-12-09 21.30.23.png


MFAですが、動かしてみたレベルでは面倒なのでオフです。

「Eメールまたは電話番号の検証を要求しますか?」についても今回は設定を全て外しますが、パスワードを忘れたときの回復手段となるので本番ではいずれかは有効化されるべきでしょう。

新規ロール名も基本的に自動設定されるままでいいでしょう。



スクリーンショット 2018-12-09 21.33.55.png


この辺も動かしてみたレベルではデフォルト値のまま次へ行きます。



スクリーンショット 2018-12-09 21.37.03.png


ここも今回は設定せず次へ。



スクリーンショット 2018-12-09 21.37.57.png


詳しくは下記に書いており私は使ったことがありませんが
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-device-tracking.html

新規アクセス時にデバイスIDを発行しsession IDのようにブラウザ側で保管して以降の通信時に送信されるものと思われます。

User Opt Inはデバイス記憶を有効にするかどうかをユーザーに確認するようですが、確認画面を別途実装する必要があるようです。

今回は「いいえ」を選択したまま次へいきます。



スクリーンショット 2018-12-09 21.38.45.png


User Poolを操作するには操作用のClientを作成する必要があります。

後で追加することもできますが、基本的にはここで作成しておきます。



スクリーンショット 2018-12-09 21.45.04.png


「クライアントシークレットの生成」にデフォルトでチェックが入っていますが、これだとユーザー作成や認証時にシークレットキーが必須で要求されてしまいます。

フロントエンドから直接認証する場合にはシークレットキーをブラウザに渡すことはセキュリティ上できませんし、ここのチェックは必ず外しておく必要があります。

バックエンドサーバーなどがCognitoを使うようなケースで初めて必要になる機能かと思います。

ここでは全てのチェックを外しておきます。



スクリーンショット 2018-12-09 21.47.00.png


先程入力したクライアント名が自動で入力された状態になります。



スクリーンショット 2018-12-09 21.51.25.png


単一のクライアントしか指定できなさそうなUIですが複数のクライアントを作成すると、こんな感じでちゃんと複数クライアントを使えるようです。


スクリーンショット 2018-12-09 21.55.17.png


認証時の様々なイベントでLambdaを発火できるようですが、今回は単純な認証しかしないので何も設定しません。



スクリーンショット 2018-12-09 21.56.18.png


User Poolの作成はここまでです。確認画面が出た後、作成完了となります。

テストユーザーを一つだけ作っておきます。AWS Managementconsoleから作るとユーザーの状態がFORCE_CHANGE_PASSWORDになってしまいます。

これを回避する方法としてはAWS CLIを使うことだそうです。

$ aws cognito-idp sign-up --client-id <アプリクライアントID> --username user1 --password xxxxxxxx 
$ aws cognito-idp admin-confirm-sign-up --user-pool-id <ユーザープールID> --username user1 
こうして作ったユーザーの状態はCONFIRMEDになっているので即座に認証を始めることができます。

続いて上部メニューよりIDプールの作成に移ります。



スクリーンショット 2018-12-09 21.58.15.png



Cognito ID Pool

適当なidプール名を入れます。

今回は認証前提のアプリとするため「認証されていないID」にはチェックを入れません。

これにチェックを入れろ、と説明してる記事も多数見受けられますが理由を説明してる記事は見たことがありません。

IDプールのIDさえ分かってしまえばセキュリティを突破できることになってしまうので無条件にONにしていいシロモノではないかと思います。

ここでは必ず認証するものとして認証プロバイダーの設定をします。

認証プロバイダーの欄は隠れていますが、アコーディオンを広げて設定します。



スクリーンショット 2018-12-09 22.01.59.png


Cognito User Pool以外にもAmazonやGoogleアカウントと紐付けれることが分かります。

ユーザープールIDとアプリクライアントIDって何やねん?って話ですが

先程作成したUser Poolの詳細を表示すると載っています。



スクリーンショット 2018-12-09 22.06.58.png


ユーザープールID

スクリーンショット 2018-12-09 22.04.25.png

アプリクライアントID



スクリーンショット 2018-12-09 22.06.19.png


次にIAMロールの設定に移ります。

上段が認証済みユーザーの場合のロール、下段が未認証ユーザーのロールです。

ここではロールの新規作成しか出来ず既存のロールを選択することはどうもできないようです。

ここで設定したロールに基づいてSTSの一時tokenがどのAWSリソースにアクセスできるのか決まるのでしょう。

使いたいAWSリソースがAPI Gatewayである場合ここでは特別な設定が要らないようです。



スクリーンショット 2018-12-09 22.56.36.png


次へ行くと作成が完了しIDプールのIDが発行されています。



スクリーンショット 2018-12-09 22.57.26.png



Lambda

今回はシンプルにHelloを返すだけの関数にしましょう



スクリーンショット 2018-12-09 23.11.28.png


コード的にはこんな感じ。



スクリーンショット 2018-12-09 23.16.09.png



API Gateway

Lambdaを呼び出すAPI Gatewayを作りましょう



スクリーンショット 2018-12-09 23.14.30.png


GETのエンドポイントを作りましょう。



スクリーンショット 2018-12-09 23.14.41.png


先程作成したLambda関数名だけ入力します。



スクリーンショット 2018-12-09 23.15.00.png


このAPI GatewayにCognito認証を設定します。新しいオーソライザーを作成します。



スクリーンショット 2018-12-09 23.16.36.png


ここでCognitoを選択します。

トークンのソースはトークンが送られてくるHTTPヘッダー名です。慣例にならってAuthorizationヘッダを使います。



スクリーンショット 2018-12-09 23.17.42.png


あとはCORSを有効化して忘れずにデプロイします。

curlからAPIを叩いてみて、認証で弾かれることを確認しておきます。

$ curl https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/stage/cognito 
{"message":"Missing Authentication Token"}   


動かしてみる


Amplify

適当なreactアプリを書いてみます。

もう午前2時半なので書き方がやっつけにも程があるのはご容赦ください:bow:

package.json
{ 
  "name": "cognito", 
  "version": "1.0.0", 
  "description": "", 
  "main": "index.js", 
  "scripts": { 
    "test": "echo \"Error: no test specified\" && exit 1", 
    "build": "webpack", 
    "start": "webpack-dev-server" 
  }, 
  "dependencies": { 
    "aws-amplify": "^1.1.13", 
    "html-webpack-plugin": "^3.2.0", 
    "lodash": "^4.17.11", 
    "react": "^16.6.3", 
    "react-dom": "^16.6.3" 
  }, 
  "devDependencies": { 
    "babel-core": "^6.26.3", 
    "babel-loader": "^7.1.5", 
    "babel-preset-es2015": "^6.24.1", 
    "babel-preset-react": "^6.24.1", 
    "webpack": "^4.27.1", 
    "webpack-cli": "^3.1.2", 
    "webpack-dev-server": "^3.1.10" 
  }, 
  "babel": { 
    "presets": [ 
      "react", 
      "es2015" 
    ] 
  } 
} 
index.html
<!DOCTYPE html> 
<html> 
<head> 
  <meta charset="utf-8"> 
</head> 
<body> 
  <div id="root"></div> 
</body> 
</html> 
app.js
import React, { PropTypes } from 'react'; 
import ReactDOM from 'react-dom'; 
import Amplify, { Auth, API } from 'aws-amplify'; 
import _ from 'lodash'; 
 
export default class Main extends React.Component { 
 
    getInitialState() { 
        return { 
            msg: '' 
        }; 
      } 
 
      componentDidMount() { 
        Amplify.configure({ 
            Auth: { 
                identityPoolId: 'ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXX',  
                region: 'ap-northeast-1', 
                userPoolId: 'ap-northeast-1_XXXXXXXXX', 
                userPoolWebClientId: 'XXXXXXXXXXXXXXXXXXX', 
            }, 
            API: { 
                endpoints: [ 
                    { 
                        name: "cognito", 
                        endpoint: "https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/stage", 
                        region: "ap-northeast-1" 
                    } 
                ] 
            } 
        }); 
 
        Auth.signIn('user1', 'password') 
        API.get('cognito', '/') 
        .then(response => { 
            this.setState({ 
                msg: response.body, 
            }); 
        }) 
        .catch(err => { 
            console.log(err); 
        }); 
    } 
 
    render() { 
        return ( 
            <div>{_.get(this.state, 'msg', '')}</div> 
        ); 
    } 
} 
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); 
const webpack = require('webpack'); 
const path = require('path'); 
 
module.exports = { 
    entry: path.resolve(__dirname, 'app.js'), 
    output: { 
      path: path.resolve(__dirname, './dist'), 
      filename: 'bundle.js' 
    }, 
    devServer: { 
      contentBase: __dirname + './index.html', 
      port: 3000, 
    }, 
    resolve: { 
      modules: [path.resolve(__dirname, "src"), "node_modules"], 
      extensions: ['.js', '.jsx'] 
    }, 
    module: { 
      rules: [ 
        { 
          test: /\.js$/, 
          exclude: /node_modules/, 
          loader: 'babel-loader', 
          query: { 
            presets: ['react', 'es2015'], 
          }, 
        } 
      ] 
    }, 
    plugins: [ 
      new HtmlWebpackPlugin({template: './index.html'}) 
    ] 
  } 
実行します

$ npm i 
$ npm run build 
$ npm start 
ブラウザでlocalhost:3000にアクセスするとLambdaの返すメッセージが表示できています。


スクリーンショット 2018-12-10 02.42.45.png



終わりに

仕組み自体は単純なのですが、実際に動かすとなるとハマりポイントが多いです。

Cognitoの解説記事が少ないわけではないんですが

各設定から実際に動くコードまで通しで説明しているものが少ない感じなので、今更ながら動かしてみた系の記事でも価値もあるのかなと思った次第です。

今回はCognito Syncには触れられなかったので次回はそこにチャレンジできればと思います。

それでは皆様よいお年をー

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)