CloudFront の前段で Lambda@Edge を利用し、Azure Entra (旧 Azure AD) の認証を通過したユーザーのみ S3 の静的コンテンツを閲覧できるようにする方法を紹介します。
前提条件
- CloudFront のディストリビューションが設定済み
- Azure Entra のサービス登録が完了し、以下の情報を取得済み
- クライアント ID
- テナント ID
- クライアント シークレット
- Lambda@Edge を CloudFront の ビューワリクエスト (Viewer Request) に設定予定
✏ Node.js (Lambda@Edge) のコード作成
1. プロジェクトセットアップ
mkdir lambda-auth && cd lambda-auth
npm init -y
npm install @azure/msal-node querystring
2. index.js を作成
const { PublicClientApplication, CryptoProvider } = require("@azure/msal-node");
const querystring = require("querystring");
// Azure Entraの設定
const config = {
auth: {
clientId: "YOUR_CLIENT_ID", // Azure EntraのクライアントID
authority: "https://login.microsoftonline.com/YOUR_TENANT_ID", // Azure EntraのテナントID
clientSecret: "YOUR_CLIENT_SECRET", // Azure Entraのクライアントシークレット
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: "Info",
},
},
};
const pca = new PublicClientApplication(config);
const cryptoProvider = new CryptoProvider();
// 認証URLを生成する関数
function getAuthUrl(state, nonce) {
return pca.getAuthCodeUrl({
scopes: ["user.read"], // 必要なスコープを指定
redirectUri: "https://your-cloudfront-domain/callback", // リダイレクトURI
state: state,
nonce: nonce,
});
}
// 認証コードを交換してトークンを取得する関数
async function getTokenFromCode(authCode, state, nonce) {
const tokenResponse = await pca.acquireTokenByCode({
code: authCode,
scopes: ["user.read"],
redirectUri: "https://your-cloudfront-domain/callback",
state: state,
nonce: nonce,
});
return tokenResponse;
}
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
// 認証コードがあるかどうかを確認
const queryString = request.querystring;
const queryParams = querystring.parse(queryString);
if (queryParams.code) {
// 認証コードがある場合、トークンを取得
try {
const tokenResponse = await getTokenFromCode(
queryParams.code,
queryParams.state,
queryParams.nonce
);
if (tokenResponse) {
// 認証成功、リクエストを継続
return request;
}
} catch (error) {
console.error("Token acquisition failed:", error);
return {
status: "401",
statusDescription: "Unauthorized",
body: "Authentication failed",
};
}
} else {
// 認証コードがない場合、認証URLを生成してリダイレクト
const state = cryptoProvider.createNewGuid();
const nonce = cryptoProvider.createNewGuid();
const authUrl = getAuthUrl(state, nonce);
return {
status: "302",
statusDescription: "Found",
headers: {
location: [
{
key: "Location",
value: authUrl,
},
],
},
};
}
};
ビルド手順と ZIP 化
zip -r lambda-auth.zip index.js node_modules package.json package-lock.json
Windows の場合:
Compress-Archive -Path index.js, node_modules, package.json, package-lock.json -DestinationPath lambda-auth.zip
AWS Lambda@Edge にデプロイ(AWS コンソール使用)
- AWS マネジメントコンソール にログイン
- Lambda サービス を開く
-
「関数の作成」 をクリック
-
関数名:
AuthLambda
-
ランタイム:
Node.js 18.x
- 実行ロール: 既存の Lambda 実行ロールを選択、または新規作成
-
関数名:
- 作成した関数の詳細ページを開き、 「コード」タブ に移動
- 「アップロード」ボタンをクリックし、
lambda-authzip
をアップロード - 「デプロイ」ボタンをクリックして反映
Lambda@Edge の設定
- Lambda 関数の詳細ページで 「アクション」→「バージョンの発行」 をクリック
- バージョン番号をメモする
- AWS マネジメントコンソールの CloudFront に移動
- CloudFront のディストリビューションを開く
- 「ビヘイビア」タブ で該当のビヘイビアを選択し、「編集」をクリック
-
「Lambda@Edge 関数の関連付け」セクションで以下を設定
-
イベントタイプ:
Viewer Request
-
Lambda 関数 ARN:
arn:aws:lambda:us-east-1:<YOUR_AWS_ACCOUNT_ID>:function:AuthLambda:<VERSION_NUMBER>
-
イベントタイプ:
- 「保存」ボタンをクリックして適用
この Lambda 関数を使用した認証の処理の流れ
Azure Entra (旧 Azure AD) の認証フローを利用して、ユーザーが認証され、認証が成功した場合にのみ S3 の静的コンテンツを表示します。
処理の流れ
-
ユーザーが CloudFront 経由で S3 のコンテンツにアクセスする
- ユーザーが CloudFront の URL(例:
https://your-cloudfront-domain/index.html
)にアクセスします。 - CloudFront はリクエストを Lambda 関数(オリジンリクエストトリガー)に渡します。
- ユーザーが CloudFront の URL(例:
-
Lambda 関数がリクエストを処理
- Lambda 関数は、リクエストに認証コード (
code
) が含まれているかどうかを確認します。 - 認証コードがない場合、Lambda 関数はユーザーを Azure Entra の認証画面にリダイレクトします。
- Lambda 関数は、リクエストに認証コード (
-
Azure Entra の認証画面にリダイレクト
- Lambda 関数は、Azure Entra の認証 URL を生成し、ユーザーをその URL にリダイレクトします。
- 認証 URL には、
state
やnonce
などのセキュリティパラメータが含まれています。 - ユーザーは Azure Entra の認証画面でログインを行います。
-
Azure Entra が認証コードを発行
- ユーザーが正しく認証されると、Azure Entra は認証コード (
code
) を生成し、指定されたリダイレクト URI(例:https://your-cloudfront-domain/callback
)にリダイレクトします。 - このリダイレクト URI は CloudFront のドメインに設定されている必要があります。
- ユーザーが正しく認証されると、Azure Entra は認証コード (
-
CloudFront が再度 Lambda 関数を呼び出す
- リダイレクト URI にアクセスすると、CloudFront は再度 Lambda 関数を呼び出します。
- この時、リクエストのクエリパラメータに認証コード (
code
) が含まれています。
-
Lambda 関数が認証コードをトークンに交換
- Lambda 関数は、認証コード (
code
) を使用して Azure Entra からアクセストークンを取得します。 - トークンの取得に成功した場合、認証は成功とみなされます。
- Lambda 関数は、認証コード (
-
認証成功時の処理
- 認証が成功した場合、Lambda 関数はリクエストをそのまま返します。
- CloudFront はリクエストを S3 オリジンに転送し、S3 の静的コンテンツをユーザーに返します。
-
認証失敗時の処理
- 認証コードの交換に失敗した場合や、トークンの取得に失敗した場合、Lambda 関数はエラーレスポンス(例: 401 Unauthorized)を返します。
- ユーザーにはエラーメッセージが表示されます。
フローの詳細
-
初回アクセス時のリダイレクト
- ユーザーが初めてアクセスした場合、認証コードがないため、Lambda 関数は Azure Entra の認証 URL を生成し、ユーザーをリダイレクトします。
- 例:
const authUrl = getAuthUrl(state, nonce); return { status: "302", statusDescription: "Found", headers: { location: [ { key: "Location", value: authUrl, }, ], }, };
-
認証コードの受け取りとトークン交換
- ユーザーが Azure Entra で認証されると、認証コードがリダイレクト URI に付与されて CloudFront に戻ります。
- Lambda 関数はこの認証コードを使用してトークンを取得します。
- 例:
const tokenResponse = await getTokenFromCode( queryParams.code, queryParams.state, queryParams.nonce ); if (tokenResponse) { return request; // 認証成功、リクエストを継続 }
-
リクエストの継続
- 認証が成功した場合、Lambda 関数はリクエストをそのまま返します。
- CloudFront はリクエストを S3 オリジンに転送し、S3 の静的コンテンツをユーザーに返します。
-
エラーハンドリング
- 認証が失敗した場合、Lambda 関数はエラーレスポンスを返します。
- 例:
return { status: "401", statusDescription: "Unauthorized", body: "Authentication failed", };
重要なポイント
- リダイレクト URI: Azure Entra のアプリケーション登録時に、リダイレクト URI を CloudFront のドメインに設定する必要があります。
-
セキュリティパラメータ:
state
やnonce
を使用して、リクエストの正当性を確認します。 -
スコープ: 必要なスコープ(例:
user.read
)を指定して、ユーザーの認証を行います。
このフローにより、ユーザーは Azure Entra で認証された後にのみ S3 の静的コンテンツにアクセスできるようになります。