ログインしていないと実行できないAPIを、Cognito UserPoolとServerless Frameworkで作る。
STEP4のUserPoolは、「メールアドレスのみでの登録」でした。 今回はユーザー名を指定できるように、あたらしくUserPoolを作り直します。
確認画面にある左側メニューから[Attributes]をクリックし、[email]の[Alias]をオンにします。
それ以外はStep4と同じ手順で作成しましょう。
一から作ると辛い目にあうのでサンプルコードをとってきます。
$ git clone [email protected]:hideokamoto/react-serverless-dashboard.git
$ cd react-serverless-dashboard
$ git checkout hands-on/start
$ npm install
$ npm run api-deploy
アプリに作成したCognitoの情報を設定します。
$ ./.bin/setup.sh
Set app name: `Your Application Name`
Set AWS region: `AWS REGION`
Set Cognito UserPoolId: `AWS Cognito UserPool Id`
Set Cognito UserPool ClientId: `AWS Cognito UserPool Client ID`
Set Your Serverless API url (https://example.execute-api.us-east-1.amazonaws.com/stage/): `Your Serverless API URL`
Create config file on client/cognito.config.js
$ npm run build
$ npm start
ここでは基本的にコピペで作業していきます。
master
ブランチに完成品がありますので、git checkout
しながら完成品と動きを比較してみてください。
まずどんな作業をするのかを見るために、serverless.ymlから変更します。
provider:
iamRoleStatements:
- Effect: "Allow"
Action:
- "*"
Resource:
- "arn:aws:cognito-idp:*:*:userpool/*"
functions:
authorizerFunc:
handler: authorizer.handler
private:
handler: private/yourname.handler
events:
- http:
path: private
method: get
integration: lambda
cors: true
authorizer:
name: authorizerFunc
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
iamRoleStatements
でLambdaに設定されたIAMのロールをカスタマイズできます。
先の例では、Cognito IdentityServiceProvider
への全アクセスを許可しています。
特定のCognitoに限定する場合は、以下のようにする
provider:
iamRoleStatements:
- Effect: "Allow"
Action:
- "*"
Resource:
- "arn:aws:cognito-idp:{REGION}:{AWS_ACCOUNT_NO}:userpool/{UserPoolId}"
authorizerFunc
という認証のためのLambda関数を定義しています。
その後、http
の設定にて、authorizer
を追加して、認証に呼び出す関数名と、渡す値を指定しています。
より詳しい解説@Qiita
functions:
authorizerFunc:
handler: authorizer.handler
private:
handler: private/yourname.handler
events:
- http:
path: private/yourname
method: get
integration: lambda
cors: true
authorizer:
name: authorizerFunc
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
続いて認証後にアクセスできるAPIのコードを実装します。
今回のコードで認証に成功している場合、event.principalId
にユーザー名が入ります。
ということでこのAPIはリクエストすると、ログインしているユーザー名をレスポンスする内容になっています。
module.exports.handler = (event, context, callback) => {
if (event.principalId === 'undefined') {
return callback(new Error('Not authorized.'))
}
const message = `Hi! Your username is "${event.principalId}". enjoy!`
const response = {message}
return callback(null, response)
}
認証用のLambdaで実行するコードを貼り付けます。
YOUR_COGNITO_USERPOOL_REGION
には自分の作成したUserPoolのリージョンを入れてください。
'use strict'
const aws = require('aws-sdk')
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
'apiVersion': '2016-04-18',
'region': 'YOUR_COGNITO_USERPOOL_REGION'
})
module.exports.handler = (event, context, cb) => {
const params = {'AccessToken': event.authorizationToken}
cognitoidentityserviceprovider.getUser(params, (err, data) => {
if (err) {
return cb(generatePolicy('user', 'Deny', event.methodArn))
}
return cb(null, generatePolicy(data.Username, 'Allow', event.methodArn))
})
}
function generatePolicy (principalId, effect, resource) {
const authResponse = {principalId}
if (effect && resource) {
const policyDocument = {
'Statement': [],
'Version': '2012-10-17'
}
const statementOne = {
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': resource
}
policyDocument.Statement[0] = statementOne
authResponse.policyDocument = policyDocument
}
return authResponse
}
手続きとしては、
- APIから送られてきたアクセストークンを使ってCognitoに一致するユーザーが存在するかを確認。
- APIがリクエスト処理するためのpolicyDocumentを付与 という流れです。
コードが用意できたらデプロイします。
$ npm run api-deploy
curlでprivate/yourname
にアクセスするとエラーになるはずです。
これはリクエストヘッダーに正しいAccessToken
が送られていないため、認証に失敗しているからです。
ということでアプリ側でAccessToken
を送るようにします。
CognitoのSDKからgetCurrentUserSession()
を実行すると、AccessToken
の値が取れます。
取得処理をよしなにラッパーしたAuth.getAuthStatus()
という関数を用意しているので、それを呼び出します。
呼び出した後は、HTTPリクエストを送信するライブラリ(今回はsuperagent)を使って、リクエストヘッダーに取得した値をつけるようにします。
getPrivate (endpoint) {
return new Promise((resolve, reject) => {
Auth.getAuthStatus().then((result) => {
if (result === 'Unauthorized') {
reject(result)
} else {
const sessionId = result
request.get(endpoint)
.set('Authorization', sessionId)
.end((err, response) => {
if (err) {
reject(err)
}
resolve(response)
})
}
})
})
}
APIを呼び出すための関数を作れたので、実際に呼び出す処理を付け足します。 Reactならではの処理がまざってるので、Reactに興味がある人以外はあまり深く考えないでください。
handleUsername (event) {
event.preventDefault()
this.setState({'username': 'loading...'})
API.getPrivate(`${apiBase}yourname`)
.then((result) => {
this.setState({'username': result.body.message})
})
.catch((err) => {
console.log(err)
})
}
$ npm run build
$ npm start
ログイン後に出る「Get your username」ボタンを押すとユーザー名が取れる
php -S localhost:8000
http://localhost:8000/register
メールに届いた認証コードと登録したユーザー名を使って以下のようなURLを作ってアクセスする。
http://localhost:8000/activation/{USERNAME}/{CODE}
http://localhost:8000/login http://localhost:8000/activation/{USERNAME}/{CODE}