本記事の目的について
前回記事ではChalice
というAWSのサービスについて記載しました。
そこではChalice
のインストールから、簡単なデバッグ方法までの説明をしてますのでまだChalice
に触れたことがない方はそちらをご覧ください。
本記事ではWebApiに認証をつける方法について紹介したいと思います。
環境
本記事の環境は
- MacOS Mojave 10.14.1
- Python 3.6.7
- chalice 1.6.1
- docker 18.09.0
で作成し、httpクライアントは
- httpie 1.0.0
を使います
ChaliceのWebApiの認証に関する機能
ここに記載がある通り、AWSでは一般的である IAM, CognitoUserPoolに加え、Custom Authorizerを使っての認証を設定することができます。
これは APIGateway が持っている オーサライザーの設定項目と同じようです。
本記事では、CognitoUserPool を使った認証について記載したいと思います。
CognitoUserPoolの準備
ChaliceにはCognitoUserPoolを作成する機能がないのでCloudFormationを使って作成したいと思います。
cloudformation ドキュメント
AWS::Cognito::UserPool
AWS::Cognito::UserPoolClient
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
CognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: ChaliceUserPool
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: True
RequireUppercase: True
RequireNumbers: False
RequireSymbols: False
AdminCreateUserConfig:
AllowAdminCreateUserOnly: True
CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: ChalicePoolClient
UserPoolId: !Ref CognitoUserPool
GenerateSecret: False
Outputs:
CognitoUserPoolArn:
Value: !GetAtt CognitoUserPool.Arn
was-cliが有効な環境で下記のコマンドを実行します。
S3には適当なバケットを作成してください。
上記のファイルが存在するフォルダで下記のコマンドを実行してください。
$ aws cloudformation deploy --template-file template.yaml --stack-name ChaliceAuthCognitoUserPool --capabilities CAPABILITY_IAM
# Waiting for changeset to be created..
# Waiting for stack create/update to complete
# Successfully created/updated stack - ChaliceAuthCognitoUserPool
Successfully...
と表示されればCognitoUserPoolが作成され、
またCloudformationのstackにCognitoUserPoolのArnが下記のように設定されているはずです。
後ほどChaliceの設定に使いますのでメモっておいてください。
認証付きWebApiの実装
認証の実装は
- プロジェクトの作成
- 環境変数の登録、読み込み
- Authorizerの実装
のステップを踏むことになります。
※ 以下のステップはchalice
コマンドが使えることを前提に記載しています。
※ 環境のClalice構築方法については前回記事を参考にしてください。
プロジェクトの作成
下記のコマンドを実行してください。
chalice new-project auth-project
$ tree -ar .
.
├── requirements.txt
├── app.py
├── .gitignore
└── .chalice
└── config.json
のように4つのファイルが出来上がります。
環境変数の登録
Chaliceはconfig.json
にenvironment_variables
のエントリーを登録するとlambdaで環境変数が呼び出せるようになります。
config.json
{
"version": "2.0",
"app_name": "auth",
"stages": {
"dev": {
"api_gateway_stage": "api",
"environment_variables" : {
"USER_POOL_ARN": "取得したUserPoolArnを指定"
}
}
}
}
上記の設定でUSER_POOL_ARN
という環境変数がlambdaの環境に登録されます。
$ chalice deploy
実行後、出来上がったlambdaを見ると
のように環境変数が登録されます。
環境変数の呼び出し
python
から環境変数を呼び出すには以下のように実装します。
import os
os.environ['USER_POOL_ID']
Authorizerの実装
すべてのパーツが揃いましたのでどんどん実装していきます。
app.py
import os
from chalice import Chalice
from chalice import CognitoUserPoolAuthorizer
app = Chalice(app_name='auth')
authorizer = CognitoUserPoolAuthorizer(
'ChaliceUserPool', provider_arns=[os.environ['USER_POOL_ARN']]
)
@app.route('/', methods=['GET'], authorizer=authorizer)
def authed_index():
# print(os.environ['USER_POOL_ARN'])
return {'success': True}
このように、用途に合わせたauthorizer(今回はCognitoUserPoolAuthorizer)をインスタンス化して@app.route
のデコレータの引数として渡せば良いようです。
非常に簡単ですね。
動作確認
まずはローカルで動作確認をしてみましょう
$ chalice local
$ http localhost:8000
# {
# "success": true
# }
ローカルサーバーを立ち上げて、Authorizerを設定したApiを呼び出すと、呼び出せてしまいました。どうやらローカルではAuthorizerの検証ができないようです。
次にAWSにデプロイしてみます。
$ chalice deploy
$ http $(chalice url)
# HTTP/1.1 401 Unauthorized
# ...
# {
# "message": "Unauthorized"
# }
のように今度はApiの呼び出しが 401
の認証エラーになったようです。
ApiGateWayを確認してみると
のようにApiGatewayにオーサライザーが作成され、対象のApiにはそのオーサライザーが COGNITO_USER_POOL
で設定されていることが確認できます。
次にCognitoUserPoolのトークンを取得して Authorization
ヘッダーに CognitoUserPool が発行したトークンを付加してリクエストを送信してみましょう。
トークンの取得
トークンの取得、今回はあらかじめユーザーをコンソール上で作成してそのユーザーに対してNodo.js用のAWSのSDKを使ってトークンを取得します。(Clientの用途に合わせて、SDKを選択してください。今回は記載していませんが、React, Android, iOS で実装したい場合はamplifyが良さそうです。)
package.json
{
"name": "nodejs",
"version": "1.0.0",
"description": "get token form your userpool",
"main": "get_tokne.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "tukky",
"license": "ISC",
"dependencies": {
"amazon-cognito-identity-js": "^2.0.27",
"node-fetch": "^2.2.0"
}
}
get_token.js
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
global.fetch = require('node-fetch');
const poolData = {
UserPoolId: '<Set Your UserPoolId>',
ClientId: '<Set Your UserPool Client Id>'
};
const userName = '<set your user name>'
const authenticationData = {
Username: userName,
Password: '<set your user pool password>'
};
const userData = {
Username: userName,
Pool: new AmazonCognitoIdentity.CognitoUserPool(poolData)
}
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
const idToken = result.getIdToken().getJwtToken();
console.log(idToken);
},
onFailure: (err) => {
console.log(err);
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
cognitoUser.completeNewPasswordChallenge(authenticationData.Password, {}, this);
}
});
get_token.js
に対象となる
- UserPoolId
- UserPoolClientId
- UserName
- UserPassword
をget_token.js
に設定してください。
package.json
と get_token.js
を同一のフォルダに配置し
$ npm install
$ node get_token.js
# eyJ...
と実行するとeyJ
から始まる目的のトークンが取得できます。
トークン付きリクエストの発行
先ほど取得したトークンを付加してリクエストしてみましょう
$ http $(chalice url) Authorization:eyJ...
# HTTP/1.1 200 OK
# {
# "success": true
# }
のように認証が成功し無事Apiが呼び出すことができました。
まとめ
Chaliceは非常に便利なのですが、機能を使いこなすためには他のAWSに関する知識も必要になります。
また、今回はCloudformationを使ってUserPoolの生成をしましたが、CI/CDツールで運用したり、デプロイ担当と開発担当が分かれているとCloudformationの実行手順や、環境変数の手順ななどのルールを決めないと実際の運用時に問題になりそうです。
(その際はconfig.jsonに自動で環境変数を追加するようなスクリプトが必要になると思います。)
このような問題を抱えながらも自分はChalice
を積極的に使っていきたいと思います。
理由は開発者観点からすると、コードと環境構築が一体になっているのは魅力的で、作りたいものがすぐに環境構築を気にせずに作れるからです。
次回はDynamoDBとS3の連携に関して記述できればと考えています。
ここまで読んでいただきありがとうございました。
開発に少しでも役立てば幸いです。
おまけ
このおまけはNodejsの環境の構築に関するものです、すでにNodejsの環境がある場合はそちらをお使いください。
Nodejsの環境構築にDockerを使うと便利です。
Nodejsの環境にはnode公式のコンテナの8.14.0-alpine
を利用しました。
docker インストール for mac
docker インストール for windows
dockerhub nodejs
Token取得用のDockerfile
FROM node:8.14.0-alpine
COPY src /node/src
WORKDIR /node/src
RUN npm install
CMD node /node/src/get_token.js
フォルダ構成
$ tree .
#.
# ├── Dockerfile
# └── src
# ├── get_token.js
# └── package.json
Docker コンテナの実行
$ docker build -t token-test:1.0 .
$ docker run --rm token-test:1.0
これで目的のtokenの取得ができます。
コンテナ上で作業したい場合は
$ docker run --rm -it token-test:1.0 /bin/sh
を実行するとshでコンテナにログインできます。
Dockerについての参考
https://qiita.com/rawHam/items/80ba13d2d2d56dba411e
https://qiita.com/yuki_ycino/items/b94ae2bf7d78685cd6f5