8
3

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.

CloudFront FunctionsでBasic認証ができるか試してみた

Last updated at Posted at 2021-05-20

はじめに

結論を申し上げるとできませんでした。悲しみが深いです。
「用法用量を守って正しくお使いください」という言葉がぴったりです。
Basic認証をCloudFrontで実装したいならLambda@Edgeを使いましょう。

参考資料:

追記(2021/05/24)

コメントをいただき、base64でエンコードできる方法が見つかったため、
Basic認証を実行することができます。

なぜBasic認証ができないのか

これはCloudFront Functionsの1 msの起動時間制限に引っかかるためだと思います。

CloudFront Functionsの用途として非常に高速でシンプルな HTTP(S) リクエスト/レスポンス操作することを目的としています。そのため、非同期処理等の時間がかかる複雑なものは対象外(Lambda@Edge使えよ)だったのだと思います。

また、使える関数も少なく、パッケージもインポートできないため、非常にシンプルな実装しかできませんでした。

参考資料:

そのため、参考資料のAWSブログで書かれている4つの用途で使うのが適任だと感じました。

  • キャッシュキーの操作と正規化
  • URL の書き換えとリダイレクト
  • HTTP ヘッダーの操作
  • アクセス承認

またサンプルコードのGitHubリポジトリがあるので、こちらを参照すると実装のイメージが湧くと思います。

Basic認証実装コード

※できないけど、実際に実装したコードを晒そうと思います。このコードではCloudFront FunctionsでBasic認証できません

CloudFront Functionsのランタイムは JavaScript(ECMAScript 5.1 compliant) と古く、使えるBuild-in modulesもCryptoとQuery stringしかないっぽいです。

参考資料:

そのため、自前でBasic認証を実現するためにBase64にする処理を自前で書く必要があります(btoa関数もないので、文字列をBase64に変換することは無理)。

function base64Encode(...parts) {
  return new Promise(resolve => {
    var reader = new FileReader();
    reader.onload = () => {
      var offset = reader.result.indexOf(",") + 1;
      resolve(reader.result.slice(offset));
    };
    reader.readAsDataURL(new Blob(parts));
  });
}

Basic認証をさせたい場合は www-authenticate ヘッダの値に Basic をいれればすることが可能なので、認証が通らない場合はこのようにします。

参考資料:

var response401 = {
    statusCode: 401,
    statusDescription: 'Unauthorized',
    headers:
    {'www-authenticate': {value:'Basic'}}
};

CloudFront Funcitonsのメイン関数はLambdaやLambda@Edgeと似てますが、 contextcallback がなく event のみ受け取ります。

function handler(event) {
   var request = event.request;
   return request;
}

最終的にこのようなコードを書きました。
base64Encodeの部分はpromiseで処理をしているため、
await/asyncを使いたかったのですが、JavaScript のバージョン制約で使えなかったです。

base64Encodeの戻り値から処理を行いresponseに代入するようにしてますが、処理時間の制約事項のため promise {} しか返して来ずBase64のエンコードはできませんでした。

var response401 = {
    statusCode: 401,
    statusDescription: 'Unauthorized',
    headers:
    {'www-authenticate': {value:'Basic'}}
};

function base64Encode(...parts) {
  return new Promise(resolve => {
    var reader = new FileReader();
    reader.onload = () => {
      var offset = reader.result.indexOf(",") + 1;
      resolve(reader.result.slice(offset));
    };
    reader.readAsDataURL(new Blob(parts));
  });
}

function handler(event) {
    // NOTE: This example function is for a viewer request event trigger. 
    // Choose viewer request for event trigger when you associate this function with a distribution. 
    // Get request and request headers
    var request = event.request;
    var headers = request.headers;
    // Configure authentication
    var authUser = 'user';
    var authPass = 'pass';
    var response = base64Encode(authUser + ':' + authPass).then((encoded) => {
        var authString =  'Basic ' + encoded;
        if (typeof headers.authorization == 'undefined' || headers.authorization.value != authString) {
          return response401;
        }
        return request;
    });
    return response;
}

コード修正版 追記(2021/05/24)

btoa 関数を使わなくても toString('base64') でエンコードすることができました。

var response401 = {
    statusCode: 401,
    statusDescription: 'Unauthorized',
    headers:
    {'www-authenticate': {value:'Basic'}}
};

function handler(event) {
    // NOTE: This example function is for a viewer request event trigger. 
    // Choose viewer request for event trigger when you associate this function with a distribution. 
    // Get request and request headers
    var request = event.request;
    var headers = request.headers;
    // Configure authentication
    var authUser = 'user';
    var authPass = 'pass';
    var authString = 'Basic ' + (authUser + ':' + authPass).toString('base64');
    if (typeof headers.authorization == 'undefined' || headers.authorization.value != authString) {
        return response401;
    }
    
    return request;
}

おわりに

Basic認証ではなく、Digest認証だとCloudFront Functionsで実装できそうでしたが、そこまで頑張るならLambda@Edgeでいいかという結論に至りました。

CloudFront Functionsで気軽にBasic認証もできるので、Lambda@Edge以外の選択肢も視野に入れてみるといいかもしれません。
※コードを読んでお気づきかと思いますが、Basic認証は文字列を単純に結合して、base64でエンコードするだけなので、めちゃくめちゃセキュリティ的に弱いので、あまり使わない方がいいです

8
3
3

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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?