本記事は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のページに行くとこのような画面になるので「ユーザープールの管理」へ
最初はリストが空なので新規作成に進みます
適当な名前を付けます。今回はステップごとに設定していきます。
今回は任意のユーザー名を使うものとし、「ユーザー名」「Eメールアドレスおよび電話番号」はデフォルトのままの設定とします。
標準属性についても今回はusernameとパスワードのみを管理するものとし、デフォルトではemailにチェックが付いていますが全て外します。
チェックが付いているとSignup時に入力を求められる他、Signin後に発行されるJWTにもクレーム情報として返ってきます。
クレームというのは「文句を言う」というニュアンスでも使われますが、「要求する」という意味もあり「必須項目として入力を求める」というニュアンスで使われているのかと思います。
ちなみにこのページで設定する項目は後で編集することができません。間違った場合は作り直しになるので注意しましょう。
パスワード強度を設定できます。「数字を必要とするs」のケツに謎のsが付いてますね・・・
動かしてみたレベルだと面倒なのでここのチェックを全部外しました。最小長は6文字がミニマムです。
MFAですが、動かしてみたレベルでは面倒なのでオフです。
「Eメールまたは電話番号の検証を要求しますか?」についても今回は設定を全て外しますが、パスワードを忘れたときの回復手段となるので本番ではいずれかは有効化されるべきでしょう。
新規ロール名も基本的に自動設定されるままでいいでしょう。
この辺も動かしてみたレベルではデフォルト値のまま次へ行きます。
ここも今回は設定せず次へ。
詳しくは下記に書いており私は使ったことがありませんが
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-device-tracking.html
新規アクセス時にデバイスIDを発行しsession IDのようにブラウザ側で保管して以降の通信時に送信されるものと思われます。
User Opt Inはデバイス記憶を有効にするかどうかをユーザーに確認するようですが、確認画面を別途実装する必要があるようです。
今回は「いいえ」を選択したまま次へいきます。
User Poolを操作するには操作用のClientを作成する必要があります。
後で追加することもできますが、基本的にはここで作成しておきます。
「クライアントシークレットの生成」にデフォルトでチェックが入っていますが、これだとユーザー作成や認証時にシークレットキーが必須で要求されてしまいます。
フロントエンドから直接認証する場合にはシークレットキーをブラウザに渡すことはセキュリティ上できませんし、ここのチェックは必ず外しておく必要があります。
バックエンドサーバーなどがCognitoを使うようなケースで初めて必要になる機能かと思います。
ここでは全てのチェックを外しておきます。
先程入力したクライアント名が自動で入力された状態になります。
単一のクライアントしか指定できなさそうなUIですが複数のクライアントを作成すると、こんな感じでちゃんと複数クライアントを使えるようです。
認証時の様々なイベントでLambdaを発火できるようですが、今回は単純な認証しかしないので何も設定しません。
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プールの作成に移ります。
Cognito ID Pool
適当なidプール名を入れます。
今回は認証前提のアプリとするため「認証されていないID」にはチェックを入れません。
これにチェックを入れろ、と説明してる記事も多数見受けられますが理由を説明してる記事は見たことがありません。
IDプールのIDさえ分かってしまえばセキュリティを突破できることになってしまうので無条件にONにしていいシロモノではないかと思います。
ここでは必ず認証するものとして認証プロバイダーの設定をします。
認証プロバイダーの欄は隠れていますが、アコーディオンを広げて設定します。
Cognito User Pool以外にもAmazonやGoogleアカウントと紐付けれることが分かります。
ユーザープールIDとアプリクライアントIDって何やねん?って話ですが
先程作成したUser Poolの詳細を表示すると載っています。
ユーザープールID
アプリクライアントID
次にIAMロールの設定に移ります。
上段が認証済みユーザーの場合のロール、下段が未認証ユーザーのロールです。
ここではロールの新規作成しか出来ず既存のロールを選択することはどうもできないようです。
ここで設定したロールに基づいてSTSの一時tokenがどのAWSリソースにアクセスできるのか決まるのでしょう。
使いたいAWSリソースがAPI Gatewayである場合ここでは特別な設定が要らないようです。
次へ行くと作成が完了しIDプールのIDが発行されています。
Lambda
今回はシンプルにHelloを返すだけの関数にしましょう
コード的にはこんな感じ。
API Gateway
Lambdaを呼び出すAPI Gatewayを作りましょう
GETのエンドポイントを作りましょう。
先程作成したLambda関数名だけ入力します。
このAPI GatewayにCognito認証を設定します。新しいオーソライザーを作成します。
ここでCognitoを選択します。
トークンのソースはトークンが送られてくるHTTPヘッダー名です。慣例にならってAuthorizationヘッダを使います。
あとはCORSを有効化して忘れずにデプロイします。
curlからAPIを叩いてみて、認証で弾かれることを確認しておきます。
$ curl https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/stage/cognito
{"message":"Missing Authentication Token"}
動かしてみる
Amplify
適当なreactアプリを書いてみます。
もう午前2時半なので書き方がやっつけにも程があるのはご容赦ください
{
"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"
]
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="root"></div>
</body>
</html>
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>
);
}
}
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の返すメッセージが表示できています。
終わりに
仕組み自体は単純なのですが、実際に動かすとなるとハマりポイントが多いです。
Cognitoの解説記事が少ないわけではないんですが
各設定から実際に動くコードまで通しで説明しているものが少ない感じなので、今更ながら動かしてみた系の記事でも価値もあるのかなと思った次第です。
今回はCognito Syncには触れられなかったので次回はそこにチャレンジできればと思います。
それでは皆様よいお年をー