概要
Amazon API Gateway の Custom Authorizerを使うと、独自の認証をLambdaファンクションで定義して、API Gatewayで作ったAPIのアクセス管理を行うことが出来ます。
Amazon API Gateway カスタム認証の概要
今回はUser Poolsで管理されているユーザでAPI認証機能を作ります。
User PoolsについてはAmazon Cognito User Poolsを使って、webサイトにユーザ認証基盤を作るで詳しく書いています。
端的に言うとUser Poolsで管理のユーザ情報でAPIを認証しようということです。単純にユーザログインが必要で会員情報をやり取りするアプリには非常に効果的に使うことが出来ると思います。
認証用のLambdaファンクション
APIへのリクエスト時にAuthorizationヘッダを受け取ります。AuthorizationヘッダーにUser Poolsのアクセストークンをセットしてリクエストを送信します。
cognitoidentityserviceprovider.getUser
メソッドで判定を行い。
エラーが返れば、API Gatewayに返すPolicy DocumentにDenyを通知。正しくユーザ情報が返れば、Allowを通知します。
const aws = require( 'aws-sdk' )
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({apiVersion: '2016-04-18',region: 'us-east-1'})
exports.handler = function(event, context) {
const params = {
AccessToken:event.authorizationToken
}
cognitoidentityserviceprovider.getUser(params, function(err, data) {
if (err) {
context.succeed(generatePolicy('user', 'Deny', event.methodArn))
} else {
context.succeed(generatePolicy(data.Username, 'Allow', event.methodArn))
}
})
}
const generatePolicy = function(principalId, effect, resource) {
const authResponse = {}
authResponse.principalId = principalId
if (effect && resource) {
const policyDocument = {}
policyDocument.Version = '2012-10-17'
policyDocument.Statement = []
const statementOne = {}
statementOne.Action = 'execute-api:Invoke'
statementOne.Effect = effect
statementOne.Resource = resource
policyDocument.Statement[0] = statementOne
authResponse.policyDocument = policyDocument
}
return authResponse
}
以下の設定画面でmethod.request.header.Authorization
を設定することで、Lambdaファンクション内のevent.authorizationToken
で送られてきたアクセストークンが取得できます。
認証を通過すると以下の様なPolicy Documentが返り、APIのバックエンド処理が実行されます。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "arn:aws:execute-api:ap-northeast-1:<Account ID>:5j2z5a7nh9/null/GET/"
}
]
}
認証されたユーザ情報をバックエンドのLambdaファンクションに渡す
認証を通ったのはいいのですが、通常は認証済みのユーザ情報をAPIのバックエンドのLambdaファンクションでも取得して処理を行いたいと思います。その時はprincipalId
を使用します。
$context.authorizer.principalId
をマッピングさせることで認証時にprincipalIdに指定した値をバックエンドで使用することが可能です。
バックエンドのLambdaファンクションは以下のようにマッピングさせた値を単純に返すものを今回はセットしました。
exports.handler = (event, context, callback) => {
callback(null, event.email);
};
実行
これでデプロイしたAPIを叩いてみましょう
何もヘッダを指定しない状態だと、401が返ってきます。
[horike@horiketakahiro-no-MacBook-Pro Documents]$curl --include https://5j2z5a7nh9.execute-api.ap-northeast-1.amazonaws.com/v1
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 26
Connection: keep-alive
Date: Thu, 05 May 2016 16:17:37 GMT
x-amzn-ErrorType: UnauthorizedException
x-amzn-RequestId: e23e564b-12dc-11e6-8e7a-099c2c59ca1e
X-Cache: Error from cloudfront
Via: 1.1 5753ad031d92fdc94452799736f8b898.cloudfront.net (CloudFront)
X-Amz-Cf-Id: oKcZn-6iyF7rcQ-LNYW696m-WYwTwm6kIsXPyyW8eTN8gXwf_hbciA==
{"message":"Unauthorized"}
次にAuthorizationヘッダにでたらめな値を付けると、認証に弾かれて403が返ってきます。
[horike@horiketakahiro-no-MacBook-Pro Documents]$curl --include https://5j2z5a7nh9.execute-api.ap-northeast-1.amazonaws.com/v1 -H 'Authorization: aaaaaaa'
HTTP/1.1 403 Forbidden
Content-Type: application/json
Content-Length: 60
Connection: keep-alive
Date: Thu, 05 May 2016 16:19:05 GMT
x-amzn-ErrorType: AccessDeniedException
x-amzn-RequestId: 15fa82a2-12dd-11e6-9ffc-5bd6f26ab516
X-Cache: Error from cloudfront
Via: 1.1 b5af8e55f6a9cb57b52430dc88fed3be.cloudfront.net (CloudFront)
X-Amz-Cf-Id: DT9Kdk7YpaAMkn_GFzXdh4T7oNlKao87pQOqCc_wfs3DqNeQGaWOVQ==
{"Message":"User is not authorized to access this resource"}
最後にAuthorizationヘッダに正しいアクセストークンを付与すると以下の用にちゃんと意図通りの結果が返ってきました。
[horike@horiketakahiro-no-MacBook-Pro Documents]$curl --include https://5j2z5a7nh9.execute-api.ap-northeast-1.amazonaws.com/v1 -H 'Authorization: eyJraWQiOiJMK1luaTlURkR1Snl3UHJDdnNLVnJRVk1iREE1WkhHZ0tHdjZGRnRsdHhRPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIzYjNlMWU5My01NmUwLTQ2ODgtYTYzMi0yMjdiYjdlZTAwZjEiLCJ0b2tlbl91c2UiOiJdhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfaXFMa2hvRFNXIiwiZXhwIjoxNDYyNDY4ODg1LCJjbGllbnRfaWQiOiIzbmppbXBxMnBhaXVlNG9mbms1bDRyOWYwdSIsInVzZXJuYW1lIjoiaG9yaWtlKzAwNUBkaWdpdGFsY3ViZS5qcCJ9.nNKEmvsosVpQM0w74Z-ruTSkyssTseI4xM2TvDxG9BEyz1bxsZhE1L14JHtKK9FXsKUYlAXAAblnkDdswSRlvge9UNmh6uPMgLUYA6jL-aZX3CNemgDHTZc0JxnsuaIF4WqnJ8T2gHtC-tCmIjUkJILIoPxMCPneklBO1rIjBEt2MQ2HBkYmkz3yfqVeE603Vr29yf3pcOpn-OcRvDq58UDr4liWI7ZR71ehgLhqmFv-qQ-_Kp6iInsUnK681nR2gvVXe1KMXKc6y5mwngBncocbx2gnqbXsyp_qQ4p93dTA6f_6CzMMeiPVVT9bK7m3pUsDASNWQhEdfbN_FZUdYksw'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 37
Connection: keep-alive
Date: Thu, 05 May 2016 16:22:06 GMT
x-amzn-RequestId: 817a58ed-12dd-11e6-8b94-0d04b870f135
X-Cache: Miss from cloudfront
Via: 1.1 dbd66f9b48a662a90decd25d25e606f5.cloudfront.net (CloudFront)
X-Amz-Cf-Id: H9AyM6Xcs1goT341YENCsGcytUEb8BlONA6oWfo8nTLdwYVjszYkJg==
{"email":"horike+005@digitalcube.jp"}
こんなに簡単にUser Poolsを使ってAPI Gatewayにユーザ認証の機能を付与することが出来ました。
ますます実用的になっていきますね!