概要
API Gateway の機能である Lambda Authorizer を Ruby で実装してみました。 オーソライザーの中身は AWS Cognito を利用したトークンベースで行っています。
デプロイには Serverless を使用しました。 コードはこちらでも公開しています。
Lambda Authorizer とは
Lambda 関数を使用して API へのアクセスを制御する API Gateway の機能です。 API Gateway Lambda オーソライザーの使用
Authorizer 関数の実装
ここでは Cognito ユーザープールのトークンを使ってクライアントを認可しています。 リクエストヘッダーに含まれるベアラートークンを検証し、成功すれば IAM ポリシーを返して API へのアクセスを許可します。
require 'jwt'
require 'open-uri'
ISS = "https://cognito-idp.#{ENV['COGNITO_USER_POOL_ID'].gsub(/(?<=\d)(.*)/, '')}.amazonaws.com/#{ENV['COGNITO_USER_POOL_ID']}"
def authorize(event:, context:)
puts 'Auth function invoked'
token = event['authorizationToken'][7..-1]
header = Base64.urlsafe_decode64(token.split('.')[0])
kid = JSON.parse(header, symbolize_names: true)[:kid]
res = OpenURI.open_uri("#{ISS}/.well-known/jwks.json")
keys = JSON.parse(res.read, symbolize_names: true)[:keys]
key = keys.find { |k| k[:kid] == kid }
pem = JWT::JWK.import(key).public_key
begin
decoded = JWT.decode token, pem, true, verify_iat: true, iss: ISS, verify_iss: true, algorithm: 'RS256'
rescue JWT::JWKError => e
puts "Provided JWKs is invalid: #{e}"
return generate_policy(nil, 'Deny', event['methodArn'])
rescue JWT::DecodeError => e
puts "Failed to authorize: #{e}"
return generate_policy(nil, 'Deny', event['methodArn'])
end
generate_policy(decoded[0]['sub'], 'Allow', event['methodArn'])
end
def generate_policy(principal_id, effect, resource)
auth_response = { principalId: principal_id }
auth_response[:policyDocument] = {
Version: '2012-10-17',
Statement: [
{ Action: 'execute-api:Invoke', Effect: effect, Resource: resource }
]
}
auth_response
end
認可後に実行される Lambda 関数の実装
def private_endpoint(event:, context:)
{ statusCode: 200, body: 'Only logged in users can see this' }
end
デプロイする
API の構築に必要な AWS のリソースを定義したファイルを作成します。
8 行目のCOGNITO_USER_POOL_ID
の値を実際のユーザープール ID に置き換える必要があります。
privateEndpoint
のevents
にあるauthorizer
でこの関数が実行される前に呼び出される Lambda Authorizer の設定を行なっています。
Serverless フレームワークを使えば簡単にこうした設定をすることができます。
service: aws-ruby-cognito-custom-authorizers
provider:
name: aws
runtime: ruby2.5
environment:
COGNITO_USER_POOL_ID: COGNITO_USER_POOL_ID
plugins:
- serverless-hooks-plugin
custom:
hooks:
package:initialize:
- bundle install --deployment
functions:
auth:
handler: auth.authorize
privateEndpoint:
handler: handler.private_endpoint
events:
- http:
path: api/private
method: get
authorizer: auth
cors:
origins:
- "*"
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
Gemfile を作成し、必要なライブラリをインストールします。
bundle init
echo \"jwt\" >> Gemfile
bundle
デプロイするには以下のコマンドを実行します。
npm install -g serverless
npm install --save serverless-hooks-plugin
sls deploy
API のテスト
curl コマンドを使って API のテストをすることができます。
<id_token>
には Cognito ユーザーの ID トークンをコピーしてください。
API の URL はデプロイ後にコンソールに表示される URL に置き換えます。
curl --header "Authorization: bearer <id_token>" https://{api}.execute-api.{region}.amazonaws.com/api/privat
おわり
AWS 上に作成したリソースは以下のコマンドで削除できます。
sls remove