CloudFront Lamba@EdgeでBasic認証する

  • 2
    Like
  • 0
    Comment

CloudFront + S3オリジン構成の場合、Basic認証を使ったコンテンツ保護が出来ませんでしたが、Lambda@Edge(現時点でプレビュー)によって可能になりました。
動作サンプル自体は公式ドキュメントにありますが、あくまでもサンプルであり、下記の点で実用に耐えません。

  • 一部のパスだけを認証の対象にできない
  • Authorizationヘッダがなかった場合に401ではなく403を返すため、ブラウザの認証ダイアログが表示されない
  • 認証エラー時にBodyが返却されないので、分かりづらい
  • 複数のクレデンシャルに対応していない
  • HTTP/2で動かない(ヘッダの大文字小文字問題)

以上の問題を修正したのが下記コードになります。パスにsecretが含まれるものだけが認証の対象です。

今のバージョンは、クレデンシャルがそのままハードコードされてるので、ソースの取り扱いにご注意を。
パスワードはハッシュをかけた方が安全ですが、これはTODOとします。

'use strict';

exports.handler = function(event, context, callback) {
  const request = event.Records[0].cf.request;

  const errorContent = '\
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\
<html><head>\
<title>401 Authorization Required</title>\
</head><body>\
<h1>Authorization Required</h1>\
<p>This server could not verify that you are authorized to access the document\
requested.  Either you supplied the wrong credentials (e.g., bad password), or your\
browser doesn\'t understand how to supply the credentials required.</p>\
</body></html>\
';

  const credentials = {
    'user1': 'pass1234',
    'user2': 'pass5678'
  }

  if (request.uri.match(/secret/i)) {
    var authorities = request.headers['Authorization'] || request.headers['authorization']
    if (authorities) {
      let authorized = false;
      for (let user in credentials) {
        var secret = new Buffer(user + ':' + credentials[user]).toString('base64');
        for (var i = 0; i < authorities.length; i++) {
          if (authorities[i].split(" ")[1] === secret) {
            authorized = true;
          }
        }
      }
      if (authorized) {
        console.log("match: " + authorities);
        callback(null, request);
      } else {
        console.log("not match: " + authorities);
        callback(null, {status: '403', statusDescription: '403 Forbidden', httpVersion: request.httpVersion,
                        headers: {
                          'Content-Type': ['text/html; charset=UTF-8'],
                        },
                        body: errorContent.toString('utf8')
                       });
      }
    } else {
      // Client did not send authorization
      callback(null, {status: '401', statusDescription: '401 Unauthorized',
                      httpVersion: request.httpVersion,
                      headers: {
                        'WWW-Authenticate': ['Basic']
                      }
                     });
    }
  } else {
    callback(null, request);
  }
}