1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LambdaのオーソライザーでBASIC認証を追加する【言語不問】

Posted at

前回作成したサーバーレスLaravelですが、BASIC認証を付与しようとしたら躓きました。
API Gatewayを経由すると、WWW-Authenticateヘッダーがx-amazn-remapped-www-authenticateに置き換えらます。その結果BASIC認証を求めるポップを表示できず無条件で401 Unauthorizedエラーだけを表示するWEBになります。

つまり問題はLambdaではなく、API Gatewayです。ALBでURLを設定すればこの問題はありません。ALB経由のLambdaを使ったサーバーレスLaravelのBASIC認証は通常のLaravelと同じです。53eda06

API Gateway経由でもBASIC認証を導入するには以下のように変更します。

  • 別途新規のLambda関数を作成し、API Gateway上にオーソライザーとして登録する。
  • オーソライザーのレスポンスにWWW-Authenticateを付与する

オーソライザー用LambdaはメインのLambdaと同じ言語である必要は無いという意味で言語不問です。この記事では新規にプロジェクトを作成し、nodejsのオーソライザーを付与します。上記Laravelはオーソライザーも素のPHPのLambda関数で実装したかったですが、うまくいかずnodejsで実装しています。cbb1ad6

しかしながら、この方法だと- http: 'ANY /{proxy+}'などダイナミックなルーティングをした場合に

An error occurred: ApiGatewayResourceUuidVarhtml - Resource's path part only allow a-zA-Z0-9._- and curly braces at the beginning and the end. 

というエラーが発生します。つまり静的なパスにしか設定できません。AWSフォーラムを見たところ、既知の問題のようです。実務アプリでは例えば401ではなく/loginにリダイレクトさせ、ブラウザーにBASIC認証用のヘッダーを持たせる責務を1つの静的パスに集約させるなどの対策が必要です。それかAPI Gatewayからロードバランサーに切り替えるなど。

$ # https://github.com/umihico/authorizer-demo/commit/c0d1263
$ mkdir authorizer-demo
$ cd authorizer-demo
$ serverless create --template aws-nodejs --name authorizer-demo #プロジェクト作成

API Gatewayを設定して、まずオーソライザー無し動作確認します。6b95a36

serverless.yml
functions:
  hello:
    handler: handler.hello
+     events:
+       - http:
+           path: hello
+           method: get
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
$ serverless deploy
$ curl https://7xcxsu7mp5.execute-api.us-east-1.amazonaws.com/dev/hello
{message: "Go Serverless v1.0! Your function executed successfully!",input: {省略}}

BASIC認証の前にトークンを使った認証を作ってテストします。dcb8407

handler.js
'use strict';


+ module.exports.auth = (event, context, callback) => {
+     var token = event.headers["Authorization"];
+     if (token == 'password') {
+         callback(null, {
+             principalId: 'user',
+             policyDocument: {
+                 Version: '2012-10-17',
+                 Statement: [{
+                     Action: 'execute-api:Invoke',
+                     Effect: 'Allow',
+                     Resource: "*",
+                 }]
+             }
+         });
+     } else {
+         callback('Unauthorized');
+     }
+ };

module.exports.hello = async event => {
  return {
    statusCode: 200,
serverless.yml
 - http:
    path: hello
    method: get
+     authorizer:
+       name: auth
+       resultTtlInSeconds: 0
+       type: request
+ auth:
+ handler: handler.auth
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
$ curl https://7xcxsu7mp5.execute-api.us-east-1.amazonaws.com/dev/hello
{message: "Unauthorized"}
$ curl --header "Authorization: password" https://7xcxsu7mp5.execute-api.us-east-1.amazonaws.com/dev/hello
{message: "Go Serverless v1.0! Your function executed successfully!",input: {省略}}
$ curl --header "Authorization: dummy" https://7xcxsu7mp5.execute-api.us-east-1.amazonaws.com/dev/hello
{message: "Unauthorized"}

正しいパスワードがセットされているときのみ動作してくれるようになりました。
WEBアクセスしたらBASIC認証のポップを出してくれるようにリソースを設定します。e167c22

handler.js
'use strict';

module.exports.auth = (event, context, callback) => {
-     var token = event.headers["Authorization"];
-     if (token == 'password') {
+     let Authorization = event.headers.Authorization;
+     if (!Authorization) return callback('Unauthorized');
+     let [username, password] = (new Buffer(Authorization.split(' ')[1], 'base64')).toString().split(':');
+     if (username === 'admin' && password === 'secret4') {
        callback(null, {
            principalId: 'user',
            policyDocument: {
serverless.yml
#    environment:
#      variable2: value2

+ resources:
+   Resources:
+     GatewayResponse:
+       Type: 'AWS::ApiGateway::GatewayResponse'
+       Properties:
+         ResponseParameters:
+           gatewayresponse.header.WWW-Authenticate: "'Basic'"
+         ResponseType: UNAUTHORIZED
+         RestApiId:
+           Ref: 'ApiGatewayRestApi'
+         StatusCode: '401'
# you can add CloudFormation resource templates here
#resources:
#  Resources:

デプロイしてURLにアクセスするとBASIC認証を求めるポップを表示してくれるようになりました。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?