Amazon API Gateway の Custom Authorizerを使い、User PoolsのユーザでAPI認証を行う

  • 85
    いいね
  • 0
    コメント

概要

Amazon API Gateway の Custom Authorizerを使うと、独自の認証をLambdaファンクションで定義して、API Gatewayで作ったAPIのアクセス管理を行うことが出来ます。
Amazon API Gateway カスタム認証の概要

今回はUser Poolsで管理されているユーザでAPI認証機能を作ります。
User PoolsについてはAmazon Cognito User Poolsを使って、webサイトにユーザ認証基盤を作るで詳しく書いています。

custom-auth-workflow.png

端的に言うと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で送られてきたアクセストークンが取得できます。
スクリーンショット 2016-05-06 0.55.47.png

認証を通過すると以下の様な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に指定した値をバックエンドで使用することが可能です。

スクリーンショット 2016-05-06 1.10.00.png

バックエンドの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にユーザ認証の機能を付与することが出来ました。
ますます実用的になっていきますね!