CX事業本部Delivery部のアベシです。
こちらの記事では、API Gateway + Lambda のREST APIに Auth0 + Lambda Authorizerの認可を導入する方法について紹介します。
前編、更編に分けて紹介します。
今回の前編ではLambda Authorizer と Auth0を使ったAPI Gatewayの保護の仕組みと、土台となるAPIのCDKコードについて紹介しようと思います。
Lambda Authorizer と Auth0を使った認可の仕組み
以下のフローで認可が行われます。
① クライアントがAuth0に認可をリクエストする。
② 認可されたらAuth0がアクセストークンを返す。
③ クライアントがAPIコールする。その際にアクセストークンをヘッダーとしてAPI Gatewayに渡す。
④ API GatewayがLambda Authorizerにアクセストークンを渡して関数を実行する。
⑤ Lambda Authorizerはアクセストークンを用いてAuth0へ公開鍵の取得をリクエストする。
⑥ Auth0が公開鍵をLambda Authorizerに返し、アクセストークンの RS256 署名の検証を実行する。
⑦ 検証が成功したらAPIを実行するために必要なポリシーを作成しAPI Gatewayに渡す。
⑧ API GatewayがLambda関数を実行する。
上で解説した内容はAuth0の以下の公式記事にも詳しく書かれてますので、参照していただければと思います。
実行環境
以下の環境で構築と動作確認しています。
項目名 | バージョン |
---|---|
mac OS | Ventura 13.2 |
npm | 9.6.0 |
AWS CDK | 2.66.1 |
API Gateway + Lambda の REST API
まずは土台となるREST APIのコードが以下となります。
Lambda統合プロキシ
を用いたのREST APIとなります。
lib/lambda-authorizer-with-auth0-stack.ts
import {
Stack,
StackProps,
aws_apigateway,
aws_lambda_nodejs,
Duration
} from 'aws-cdk-lib';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export class LambdaAuthorizerWithAuth0Stack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
//HelloWorldするLambdaの作成
const nameHelloWorldFunc = "Hello_world_func" // 名前にspaceが使えないので注意
const registerTaskFunc = new aws_lambda_nodejs.NodejsFunction(
this,
nameHelloWorldFunc,
{
runtime: Runtime.NODEJS_18_X,
functionName: nameHelloWorldFunc,
entry: 'src/lambda/handlers/hello-world-func.ts',
timeout: Duration.seconds(25),
logRetention: 30,
},
);
// API Gateway RestAPIの作成
const nameRestApi ="Rest API with Lambda auth";
const restApi = new aws_apigateway.RestApi(this, nameRestApi, {
restApiName: `Rest_API_with_Lambda_auth`,
deployOptions: {
stageName: 'v1',
},
});
//API Gatewayにリクエスト先のリソースを追加
const restApiHelloWorld = restApi.root.addResource('hello_world');
//リソースにGETメソッド、Lambda統合プロキシを指定
restApiHelloWorld.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(registerTaskFunc)
);
}
}
コードの解説
- Lambdaのビルド方法定義
API Gatewayの後続のLambda関数のビルド方法の定義します。
aws_lambda_nodejsのNodejsFunction
クラスを使用してビルドします。
ランタイムはNode.js18です(CDKのバージョンが古いと指定できません)。Node.jsはバージョン16が2023年9月11日でサポート終了なのできれば18を使いましょう。
あと、functionNameプロパティですが、名前にspaceが使えないです。私はよく忘れてひっかかってしまってます。
Bringing forward the End-of-Life Date for Node.js 16
- Rest APIの作成
aws_apigatewayのRestApi
クラスを使って定義します。
LambdaとのリクエストメッセージとレスポンスのやりとりにはLambda統合プロキシ
を使用しています。
メソッドの定義のところでLambdaIntegrationクラスを使用して定義しています。
restApiHelloWorld.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(registerTaskFunc)
);
Lambdaからのレスポンスは決められたJSON形式で返す必要があります。
レスポンスに使えるプロパティは以下となります。
{
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headerName": "headerValue", ... },
"multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
"body": "..."
}
この内必須項目はstatusCode
のみとなります。
Lambda統合プロキシ
を使用しない場合はLambdaへリクエストメッセージのどの部分を渡すのか、Lambdaからのレスポンスにどんな項目を返すのかを指定するために、モデルとマッピングテンプレートの定義が必要です。
実際にどちらも試すとLambda統合プロキシ
のほうが如何に楽にREST APIが作れるか解ると思います。
デプロイ
$ cdk deploy --require-approval never '*'
Bundling asset LambdaAuthorizerWithAuth0Stack/Hello_world_func/Code/Stage...
...
Outputs:
LambdaAuthorizerWithAuth0Stack.RestAPIEndpointB14C3C54 = https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/
AWS CLIでデプロイするとAPI GatewayのURLが表示されますので控えておきます。
curl コマンドでAPIを叩いてみましょう。
curl -X GET \
https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hello_world
出力
Hello World!!
CDKのコードにLambda Authorizerを追加
先程のコードにLambda Authorizerの導入に必要な定義を追加します。
追加後のコードが以下となります。
lib/lambda-authorizer-with-auth0-stack.ts
import {
Stack,
StackProps,
aws_apigateway,
aws_lambda_nodejs,
aws_iam,
Duration,
} from 'aws-cdk-lib';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export class LambdaAuthorizerWithAuth0Stack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const nameHelloWorldFunc = "Hello_world_func"
const registerTaskFunc = new aws_lambda_nodejs.NodejsFunction(
this,
nameHelloWorldFunc,
{
runtime: Runtime.NODEJS_18_X,
functionName: nameHelloWorldFunc,
entry: 'src/lambda/handlers/hello-world-func.ts',
timeout: Duration.seconds(25),
logRetention: 30,
},
);
// Lambda Authorizer用のLambda関数
const nameLambdaAuthorizerFunc = "Lambda_Authorizer_Function"
const lambdaAuthorizerFunc = new aws_lambda_nodejs.NodejsFunction(
this,
nameLambdaAuthorizerFunc,
{
functionName: nameLambdaAuthorizerFunc,
entry: 'src/lambda/handlers/lambda-authorizer.ts',
runtime: Runtime.NODEJS_18_X,
timeout: Duration.seconds(25),
logRetention: 30,
environment: { //トークンの検証に必要なデータを環境変数に格納
AUDIENCE: "https://**********.execute-api.ap-northeast-1.amazonaws.com",
JWKS_URI: "https://your_tenant.auth0.com/.well-known/jwks.json",
TOKEN_ISSUER: "https://your_tenant.auth0.com/"
},
},
);
const restApi = new aws_apigateway.RestApi(this, 'RestAPI', {
restApiName: `restApi`,
deployOptions: {
stageName: 'v1',
},
});
// Lambda Authorizerの定義
const lambdaAuth = new aws_apigateway.TokenAuthorizer(
this,
'lambdaAuthorizer',
{
authorizerName: 'lambdaAuthorizer',
handler: lambdaAuthorizerFunc,//ここでLambda Authorizer用のLambda関数を割り当てる
identitySource: aws_apigateway.IdentitySource.header('Authorization'),//アクセストークンを渡すためのヘッダーを指定
},
);
const restApiTasks = restApi.root.addResource('hello_world');
restApiTasks.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(registerTaskFunc),
{
authorizer: lambdaAuth, // 定義したLambdaAuthorizerを指定
},
);
}
}
コードの解説
追加したコードについて説明します。
- Lambda Authorizer用のLambda関数のビルド定義
こちらもNodejsFunctionクラスつかったビルドを定義しています。
アクセストークンのRS256署名を検証するの際に使う値を環境変数に指定してます。
この変数については後編で説明します。
const lambdaAuthorizerFunc = new aws_lambda_nodejs.NodejsFunction(
this,
nameLambdaAuthorizerFunc,
{
functionName: nameLambdaAuthorizerFunc,
entry: 'src/lambda/handlers/lambda-authorizer.ts',
runtime: Runtime.NODEJS_18_X,
timeout: Duration.seconds(25),
logRetention: 30,
environment: { //トークンの検証に必要なデータを環境変数に格納
AUDIENCE: "https://**********.execute-api.ap-northeast-1.amazonaws.com",
JWKS_URI: "https://your_tenant.auth0.com/.well-known/jwks.json",
TOKEN_ISSUER: "https://your_tenant.auth0.com/"
},
},
);
- Lambda Authorizerの定義
TokenAuthorizerクラスを使用してAPI Gatewayの認可にLambda Authorizerを使用するように定義しています。
handlerプロパティに前段で作成したLambda Authorizer用のLambda関数を割り当てます。
identitySourceプロパティにはどのヘッダーにアクセストークンを持たせるか指定します。
ここではAuthorization
ヘッダーを指定しています。
const nameLambdaAuth = 'lambdaAuthorizer'
const lambdaAuth = new aws_apigateway.TokenAuthorizer(
this,
nameLambdaAuth,
{
authorizerName: nameLambdaAuth,
handler: lambdaAuthorizerFunc,// ここでLambda Authorizer用のLambda関数を割り当てる
identitySource: aws_apigateway.IdentitySource.header('Authorization'),
},
);
- メソッドに認可方法を指定
前段でTokenAuthorizerクラスを使って定義したLambda Authorizer
をauthorizer
プロパティに指定しています。
restApiTasks.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(registerTaskFunc),
{
authorizer: lambdaAuth, // 定義したLambda Authorizerを指定
},
);
CDKのコードについての解説は以上です。
後編で紹介する内容
次回の後編では以下を紹介いたします。
- Auth0側の設定
- Lambda AuthorizerのLambda関数の紹介と解説
- 認可の動作確認
前編は以上、後編に続く。。。
後編
以下のブログが後編となります。