Amazon API GatewayにBasic認証をかける方法


更新

2020/09/21

event['headers']の中身が変わってた

-event['headers']['authorization']
+event['headers']['Authorization']

はじめに

ちょっとしたテスト用のAPIをAmazon API Gatewayで作ったのですが、あんまり外部公開はしたくない…でもガッチガチなセキュリティが必要なほど重要なものでもない…
というわけでBasic認証をかける方法を調べたメモです。
いくつかハマリポイントがありました。

大まかな流れ

API GatewayでBasic認証する大まかな流れ。

  1. API Gatewayのオーソライザーに認証をするLambda関数を指定する。
  2. オーソライザーのIDソースにauthorization (header)を設定する。
  3. API Gatewayの「ゲートウェイのレスポンス」の401レスポンスにWWW-Authenticateヘッダーを追加する。
  4. メソッドリクエストの設定でオーソライザーを指定する。

Lambda関数の作成

AWS Lambdaのダッシュボードから関数を作成します。Rubyが好きなのでここではRubyを選択しています。

名前は適当に、実行ロールは「基本的な Lambda アクセス権限で新しいロールを作成」でOKです。

lambda-authorizer-basic-auth
require 'aws-sdk-kms'
require 'base64'

def lambda_handler(event:, context:)
  if basic_auth(event)
    # Basic認証で認可する場合は次のようなIAMポリシーを返す
    {
      'principalId': 'user',
      'policyDocument': {
        'Version': '2012-10-17',
        'Statement': [
          {
            'Action': 'execute-api:Invoke',
            'Effect': 'Allow',
            'Resource': event['methodArn']
          }
        ]
      }
    }
  else
    # 認可しない場合は例外を送出する
    raise 'Unauthorized'
  end
end

# Basic認証する適当メソッド
def basic_auth(event)
  # ユーザー名とパスワードはLambdaの環境変数に暗号化して保存してある。
  user = Aws::KMS::Client.new.decrypt({ ciphertext_blob: Base64.decode64(ENV['user']) }).plaintext
  password = Aws::KMS::Client.new.decrypt({ ciphertext_blob: Base64.decode64(ENV['password']) }).plaintext

  # event['headers']['Authorization']にBasic認証の認証情報が入っている
  auth_header = event['headers']['Authorization']

  # 適当に認可
  auth_str = 'Basic ' + Base64.strict_encode64("#{user}:#{password}")
  auth_header == auth_str
end

オーソライザーのドキュメントはこちらにあります。
ドキュメントの例がNode.jsだったので、Rubyの場合は認可しない場合に何を返せばいいのかわからず悩んだのですが、raiseしてエラーを送出すれば良いようです。
(Node.jsのcallbackの引数をちゃんと調べればわかることでした)

オーソライザーの設定

API Gatewayのダッシュボードから次のようにオーソライザーを設定します。

ポイントはIDソースにauthorizationヘッダーを設定することです。これによりブラウザなどからのBasic認証を伴うリクエストをLambda関数に渡すことができます。

401レスポンスの設定

Basic認証なしでAPIにアクセスした場合に401エラーを返しますが、そのときにBasic認証が必要であることをブラウザなどに伝える必要があります。これがないと、ブラウザのBasic認証ダイアログなどが出ないので困ります。

「ゲートウェイのレスポンス」→「権限がありません」→レスポンスヘッダーにWWW-Authenticate、値に'Basic'を追加します。静的な値の場合は''で囲む必要があります。

メソッドの設定

あとはBasic認証をかけたいAPIメソッドの設定で、認可の方法に先のオーソライザーを指定するだけです。

その他

オーソライザーやレスポンス、メソッドの設定を変えた後はデプロイしないとAPIに反映されないので気をつけましょう(n敗)

おわりに

AWS勉強中なのですが色々わからない概念が多くてつらいです。
記事のコード中のコメントにIAMロールを返すとかもっともらしく書いてありますが、それが何かはよくわかっていない模様。

今回はブラウザからAPIにアクセスする必要があったのでBasic認証にしましたが、アプリケーションなどからアクセスする場合はAPIキー使ったほうが楽だと思います。