この記事では、Auth0を使ってAPI Gatewayに認証認可をかける方法をハンズオン形式で紹介します。
最終的に、トークン認証されたAPI Gatewayを構築し、ログインしたユーザーのみがAPIリクエストが通る状態を目指します。
フロントとバックエンドを作成します。
機能
モリモリです!
Auth0でとりあえずやりたいだろう事を詰めました!
【ログイン機能】
- メールアドレスやGoogleアカウントでログイン出来る
【認証・認可】
- いいね数のGET … 誰でも可能
- いいねのPOST … ログインしているユーザーのみ可能(認証)
- いいねのDELETE … 特別な権限のあるユーザーのみ可能(認可)
【ゴールド会員になる・辞める】
- LambdaからAuth0のAPIを叩きユーザーにRoleの追加・削除
- クライアント(React)がアプリにログイン
- Auth0がアクセストークンをクライアントに発行
- アクセストークンを持ってAPIGatewayにリクエスト
- LambdaAuthorizerがアクセストークンをチェック
- Auth0が発行した正しいトークンか?
- 必要なパーミッションを持ってるか?
- チェックを通過したリクエストのみLambdaにアクセス
下記インストール済みであること
30分 ~ 1時間ぐらいの内容として想定しています。
- DynamoDBのテーブル作成(AWSマネコン)
- Application・API作成(Auth0)
- フロントアプリ(React)のDL
- AWS SAMプロジェクト(API+Lambda)のDL
- Roleの追加・削除
- LambdaでManagementAPIを操作
動画版も作成しました!
API GatewayをAuth0で保護する!認証&認可の実践ハンズオン
「とりあえず作業を眺めたい」「動画で確認しながら手を動かしたい」と言う方は是非
マネコンでDynamoDBに移動
テーブルの作成
- テーブル名
- パーティションキー
- ソートキー
- テーブル設定
項目を探索 => 項目を作成
項目を作成
- name
- count
- 0(数値 ※文字列にしないよう注意)
✅ ここまででAWS・マネコンでの設定は完了です!
Applicationの作成
+ Create Application
- Name
- My Auth0 App(他の名前でも大丈夫です)
- Choose an application type
- Single Page Web Applications
Application URIs
ログイン後、どこにリダイレクトするかなどの設定
settingsタブに移動
Application URIsまでスクロール
Allowed Callback URLs
ログイン成功後にリダイレクトさせたいURL
http://localhost:5173/
※フロントアプリケーションのURL。Vite + Reactで作る前提
Allowed Logout URLs
ログアウト後にリダイレクトさせたいURL
http://localhost:5173/
※フロントアプリケーションのURL。Vite + Reactで作る前提
Allowed Web Origins
Auth0のAPIにアクセスできるドメインを設定する場所
http://localhost:5173/
※フロントアプリケーションのURL。Vite + Reactで作る前提
Save Changesする
以上URLの設定が出来たら 「Save Changes」 して下さい。
Apiの作成
+ Create API
- Name
- Identifier
- my-auth-api-on-apigateway(※)
- JSON Web Token(JWT)Profile
- JSON Web Token(JWT)Signing Algorithm
※認証をかけたいAPIのドメインを入れるのが慣例。今回ならばAPIGatewayのドメインを本来なら入れたいが、まだ作っていない、かつ、話を簡単にするために、今回は、適当な文字列を入れています。
PermissionsタブでAdd a Permission
↓作成するとList of Permissionsに現れる
RBAC Settingsを有効にする
Settingsタブに移動
RBAC Settingsまでスクロール
以下2つをオンにする。
- Enable RBAC
- Add Permissions in the Access Token
Save
上記2つをオンにしたら忘れずにSaveする。
✅ ここまででAuth0側の設定は完了です!ログイン機能の準備が出来ました!
GithubからコードをDL
下記よりDLしてください。
Auth.jsxを編集
下記の空文字の部分を埋めたいです。
Auth0Provider
domain=""
clientId=""
authorizationParams={{
redirect_uri: window.location.origin,
audience: "",
}}
>
App />
Auth0Provider>
domeinとclientId
- Applications => My Auth0 Appを選択
- Settingsタブに移動
下図のDomainとClient IDをコピーしてコードにペースト
audience
My Auth0 Apiの「API Audience」の値をコピーしてAuth.jsxのaudienceにペースト
貼り付けた後のイメージ…
私の場合は下のようにセットしました。
Auth0Provider
domain="dev-kq7mz3sahxev7c5d.us.auth0.com"
clientId="TMBk4DmTl8CQP2OLudi5khG2zU6hJoCz"
authorizationParams={{
redirect_uri: window.location.origin,
audience: "my-auth-api-on-apigateway",
}}
>
App />
Auth0Provider>
アプリにログインしてみる
ここまででログイン機能は出来てるので試してみましょう。
ライブラリをインストール
アプリを立ち上げる
下記のようにhttp://localhost:5173/ でアプリが立ち上がります。
VITE v6.2.2 ready in 304 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
http://localhost:5173/ に飛ぶと下記画面が表示されます。
ログインする
Continue with Google
アカウントと紐付けを許可
ログイン出来た!
しかし、「いいね」しようとしても何も起きません。
そりゃそう。
まだ、「いいね」リクエストの送信先のAPIを作成していない。
ログインするだけならばこれでOK
ログインするだけならばこれでOK!
ここからAPIGatewayをその認証と合わせて作成して行きます!
完成済みのコードを用意しています。
DLする形で進めます。また、コードには、いいねを増やすなどの関数などが含まれますが、その中身には本稿の目的から逸れるため触れません。
手順
- GithubからコードをDL
- sam build
- sam deploy
- フロントからAPIリクエスト
- Authorizer関数は何をしているか?
GithubからコードをDL
下記よりDLしてください。
sam build
コードの変更は特にありません。一旦、そのままビルド&デプロイします。
動くものを確認したいです。
(私の作業環境でビルドしてるイメージ)
sam deploy
色々聞かれるので答えて行く。
- Stack Name
- AWS Region
- Parameter DynamoTableName
- Parameter DynamoKeyName
- Parameter Auth0Domain
- My Auth0 AppのDomain(※)
- Parameter OIDCClientId
- My Auth0 AppのClient ID(※)
あとはEnterでOKです。
(私の作業環境でのイメージ)
Stack Name [sam-app]: auth0-api-gateway-main
AWS Region [us-east-1]: ap-northeast-1
Parameter DynamoTableName []: counters
Parameter DynamoKeyName []: like_counter
Parameter Auth0Domain []: dev-kq7mz3sahxev7c5d.us.auth0.com
Parameter OIDCClientId []: TMBk4DmTl8CQP2OLudi5khG2zU6hJoCz
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: N
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]: samconfig.toml
SAM configuration environment [default]: default
デプロイ成功すると下記のように成功のメッセージとAPIのURLが表示されます。
✅ APIとLambdaをデプロイ出来ました!APIに認証がかかってるかのチェックは「フロントからAPIを叩く」のあとで行います。
フロントからAPIを叩く
APIのURLをメモ
上記作成されたAPIのURLをメモしておきます。
フロントのコードを編集
- src/App.jsxの7行目ぐらい
API_DOMAINをAPIのURL(https://APIドメイン/dev)で置き換えて下さい。
// イメージ
const API_DOMAIN = "https://zy0t5ravoe.execute-api.ap-northeast-1.amazonaws.com/dev";
いいね数を取得する・いいねする
フロントアプリをリロードして「いいね」するとカウントアップされます。
Lambdaを起動するため、ラグがあるかもしれませんが、気にしないで下さい。
DynamoDBを確認すると、しっかり増えてるのが確認出来ると思います。
APIに認証がかかってるかチェックしたい
Consoleに出力されたアクセストークンを出しています。
このアクセストークンを使ってCurlする事dチェックします。
アクセストークンとAPIドメインを自分のものに置き換えて試してみて下さい。
curl -i -X POST \
-H "Authorization: Bearer アクセストークン" \
https://APIドメイン/dev/likes
参考までに私は以下のように実行しました。
(成功パターン)
curl -i -X POST \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1QZDNZYXpwR3VwSk9nVjlRMVpGWiJ9.eyJpc3MiOiJodHRwczovL2Rldi1rcTdtejNzYWh4ZXY3YzVkLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODc4ODkzNDY1NzQwNjM4NTgwMiIsImF1ZCI6WyJteS1hdXRoLWFwaS1vbi1hcGlnYXRld2F5IiwiaHR0cHM6Ly9kZXYta3E3bXozc2FoeGV2N2M1ZC51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzQ1NjQ1NjM2LCJleHAiOjE3NDU3MzIwMzYsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJUTUJrNERtVGw4Q1FQMk9MdWRpNWtoRzJ6VTZoSm9DeiIsInBlcm1pc3Npb25zIjpbXX0.ivevVtk6zAbepAzNhKcz8SH1QphHfZUg-UZGYZTVKSkCpMdRM7B9RJqE-J7cwFiBguOWr9ivSR8_3bNFe8Ckmps1XLLV0GNWAQJ-KMna70dL4rr2IWaD1lAECFzUFpmbfImy_vAJt5AmKUQHcfhic8WnvsY0G4KtLlVYw-zK8RBr3ydiEort_vFlss6gOqb2ssYzo7ya1OxFADvDZ2jJmNmd2ZAVUDwwthBwELQyh-fwQTT7A9g72uSnqchsq47UjbWg4icQFBPnKtJIBt175FpRP-8L_mc3i6XcCUXhFIKxKipf572ccqbTKqbSPbNgsx7nWf0UcNQXoUkwXlmAlQ" \
https://zy0t5ravoe.execute-api.ap-northeast-1.amazonaws.com/dev/likes
HTTP/2 200
content-type: application/json
content-length: 12
date: Sat, 26 Apr 2025 05:34:36 GMT
x-amzn-trace-id: Root=1-680c7069-4c81b9415bae3ce3152f73c9;Parent=0d08df49e5a1b327;Sampled=0;Lineage=2:53d5a100:0
x-amzn-requestid: a1521190-cbed-46c9-b1de-ab417f7a44f8
access-control-allow-origin: *
x-amz-apigw-id: JnaAiHF1NjMEmHQ=
access-control-allow-methods: POST
x-cache: Miss from cloudfront
via: 1.1 abe247adaab2cff314bfe6787604d9ea.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P3
x-amz-cf-id: gdm6rKn8nM1nn1HHUPGR5HLjNgJmOGMNb67iqsXODhrya6db1fjI2w==
{"count":11}
(失敗パターン)
トークンを1文字変更してみる。
curl -i -X POST \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1QZDNZYXpwR3VwSk9nVjlRMVpGWiJ9.eyJpc3MiOiJodHRwczovL2Rldi1rcTdtejNzYWh4ZXY3YzVkLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODc4ODkzNDY1NzQwNjM4NTgwMiIsImF1ZCI6WyJteS1hdXRoLWFwaS1vbi1hcGlnYXRld2F5IiwiaHR0cHM6Ly9kZXYta3E3bXozc2FoeGV2N2M1ZC51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzQ1NjQ1NjM2LCJleHAiOjE3NDU3MzIwMzYsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhenAiOiJUTUJrNERtVGw4Q1FQMk9MdWRpNWtoRzJ6VTZoSm9DeiIsInBlcm1pc3Npb25zIjpbXX0.ivevVtk6zAbepAzNhKcz8SH1QphHfZUg-UZGYZTVKSkCpMdRM7B9RJqE-J7cwFiBguOWr9ivSR8_3bNFe8Ckmps1XLLV0GNWAQJ-KMna70dL4rr2IWaD1lAECFzUFpmbfImy_vAJt5AmKUQHcfhic8WnvsY0G4KtLlVYw-zK8RBr3ydiEort_vFlss6gOqb2ssYzo7ya1OxFADvDZ2jJmNmd2ZAVUDwwthBwELQyh-fwQTT7A9g72uSnqchsq47UjbWg4icQFBPnKtJIBt175FpRP-8L_mc3i6XcCUXhFIKxKipf572ccqbTKqbSPbNgsx7nWf0UcNQXoUkwXlmAl" \
https://zy0t5ravoe.execute-api.ap-northeast-1.amazonaws.com/dev/likes
HTTP/2 403
content-type: application/json
content-length: 82
date: Sat, 26 Apr 2025 05:34:58 GMT
x-amz-apigw-id: JnaEXE7WNjMEIMg=
x-amzn-requestid: 3d2f8cfd-f445-4a6f-ae97-82437585f23c
x-amzn-errortype: AccessDeniedException
x-cache: Error from cloudfront
via: 1.1 880e379e1a56b21426587959131bb422.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P3
x-amz-cf-id: 0jkDmaSYQ6QFXEOld4iuBWUPUUFOUDpfkEmB3_S1jG357uIvDIQQaw==
{"Message":"User is not authorized to access this resource with an explicit deny"}
✅ APIに認証がかかってることを確認出来ました!続いて認可の話に進んで行きます!
いいねを取り消したい(数を減らしたい)
フロントの画面に戻ります。
現状だとを押しても数が減りません。
開発者ツールを開くとエラーが出てるのを確認出来ると思います。
なぜか?
ログインしてるユーザーに権限が無いからです。
Authorizer関数は何をしているか?
先に、公式ブログで実装を紹介してくれています。本稿のAuthorizerの実装は、このブログを元にして自分なりに噛み砕いたもとなっています。
まず、リクエストを通すか通さないかは以下で決まります。
- Authorizer関数はIAMポリシーをレスポンスする
- APIパスのarnに対してAllowポリシーならば許可
- APIパスのarnに対してDenyポリシーならば拒否
Authorizer関数が何をするか?
アクセストークンを受け取り、トークン検証する。結果に応じてAllow、もしくは、Denyポリシーを返す。
本稿のコード(src/authorizer.mjs)ではトークン検証をどのようにしてるか?
次の式が成り立てばトークンは正しいと言えます。
JWTの署名部 ÷ 公開鍵 = JWTのヘッダー + JWTのペイロード × sha256
// ÷とか×はあくまでイメージです。
実際のコードで見ると以下です。やってることは次のようなこと。
- トークンをヘッダー・ペイロード・署名部に分割
- 公開鍵を取得
- これらを使って上記の式が成り立つか検証(crypt.verify)
src/authorizer.mjs(抜粋)
const [headerB64, payloadB64, signatureB64] = token.split(".");
const header = JSON.parse(base64UrlDecode(headerB64));
// 使用している鍵のID
const kid = header.kid;
// JWKsと照らして公開鍵を特定
const key = jwks.keys.find((key) => key.kid === kid);
if (!key) {
return generatePolicy(methodArn, "Deny");
}
// 証明書から公開鍵を作成
const publicKey = crypto.createPublicKey({
key: `-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE-----`,
format: "pem",
});
const data = `${headerB64}.${payloadB64}`;
const signature = Buffer.from(signatureB64, "base64");
/**
* 1. data を sha256 でハッシュ
* 2. signature を publicKey で復号
* 3. 両方を比較して、一致していれば true
*/
const sig_valid = crypto.verify(
// ★デジタル署名の検証を行う関数
"sha256",
new TextEncoder().encode(data), // ハッシュ対象のデータ、文字列 → バイト列
publicKey, // 検証に使う公開鍵
signature // 検証対象の署名
);
crypto.verifyの結果がtrueならば、さらにトークン内のpermissionのチェックなどに進みます。
いいねの取り消しが何故出来なかったか?
いいねの取り消しのリクエストだった場合は、以下のように、permissionsにdelete:likeがあるかどうかのチェックを入れてるからです。
そして、現状ではdelete:likeがトークンの中に入っていません。
src/authorizer.mjs(抜粋)
// いいねの取り消しの場合はpermissionsをチェックする
if (resourcePath.startsWith("like") && httpMethod === "DELETE") {
if(!payload["permissions"].includes("delete:like")) {
return generatePolicy(methodArn, "Deny");
}
}
どうしたら、トークンにdelete:likeを含められるかと言う事をここからして行きます!
方針
- Roleを作成する
- RoleにPermissionを紐付ける
- ユーザーにRoleをくっ付ける
Roleを作成する
User management => Roles
+ Create Role
RoleにPermissionを紐付ける
Permissinsタブを開く
Add Permissions
- My Auth0 Apiを選ぶ
- delete:likeにチェック
- Add Permissionsボタンをクリック
ユーザーにRoleをくっ付ける
User Management => Users => ログインしてるユーザーを選択
Rolesタブを開く
Assign Roles
gold_memberを選んで「Assign」する
いいねを取り消す
アプリに戻り、リロード、アクセストークンを取得し直す。
その上で、すると、今度はいいねの数が減っていきます。
アクセストークンの中身を確認
コンソールにアクセストークンを出力してます。
delete:likeのPermissionがある事を確認出来ます。
もし「アクセストークンにPermissionの項目が無いぞ」と言う方は「Enable RBAC」が有効になってるかチェックして下さい。
My Auth0 APiを作成した際に下記のように設定した部分です。
✅ ここまででAPIGatewayをAuth0で認証認可する実装が出来ました!
ここからは、先ほど手動で行ったユーザーへの権限の追加をLambdaから出来るようにして行きます。
先ほどアカウント画面で直接ユーザーにロールを追加しました。
これをLambdaで実行出来るようにしていきます。
LambdaからManagement APIを操作することで可能です。
Management APIとは?
Auth0のアカウントを作成るとデフォルトである「Auth0 Management API(System API)」を使う事で、アカウント画面で行うような操作をAPIリクエストで行えます。
ユーザーにRoleを追加するという事も可能です。
Management APIのドキュメント
本稿で使用するAPI操作のドキュメントは下記です。
Management APIのアクセストークンを取得したい
Auth0 Management API(System API)を操作するためには、専用のアクセストークンが必要です。
そのアクセストークンをLambda内で取得するには「M2M Application」を作る必要があります。
M2M ApplicationはSystem APIのトークン発行するプログラムみたいなイメージです。
流れとしては以下の感じです。
M2M Applicationを作成
↓
Lambda内でM2M Application使用しアクセストークンを取得
↓
Auth0 Management API(System API)を操作
M2M Applicationを作成
+ Create Application
- Name
- My Auth0 M2m App(※他の名前でもOK)
- Choose an application type
- Machine to Machine Applications
APIとPermissionsの紐付け
- 「Auth0 Management API」を選択
- 「update:users」にチェック
LambdaからMy Auth0 M2m Appを使うとPermissonsに「update:users」が入ったアクセストークンを取得出来るようになります。
コードの変更
AWS SAMプロジェクトのtemplate.yamlに2箇所追記します。
Parametersを置き換える
元々DLしたものに3つ追加しています。置き換えて下さい。
Parameters:
DynamoTableName:
Type: String
DynamoKeyName:
Type: String
Auth0Domain:
Type: String
OIDCClientId: # React => API
Type: String
GoldMemberRoleId:
Type: String
M2MClientId: # Lambda => システムAPI
Type: String
M2MClientSecret:
Type: String
Lambdaの環境変数追加
上で受け取ったパラメーターをLambda内で使えるように環境変数を追加しています。該当箇所を置き換えて下さい。
Globals:
Function:
Timeout: 30
Environment:
Variables:
DYNAMO_TABLE_NAME: !Ref DynamoTableName
DYNAMO_KEY_NAME: !Ref DynamoKeyName
AUTH0_DOMAIN: !Ref Auth0Domain
OIDC_CLIENT_ID: !Ref OIDCClientId
GOLD_MEMBER_ROLE_ID: !Ref GoldMemberRoleId
M2M_CLIENT_ID: !Ref M2MClientId
M2M_CLIENT_SECRET: !Ref M2MClientSecret
ビルド&デプロイ
下記コマンドでビルドします。
下記コマンドでデプロイします。
新しく追加したパラーメターについて聞かれます。
場所は下図を参考にして下さい。
- M2MClientId
- M2MClientSecret
My Auth0 M2M AppのClient IDとClient Secretを設定して下さい。
参考までに私の場合は以下のように設定しました。
Stack Name [auth0-api-gateway-main]:
AWS Region [ap-northeast-1]:
Parameter DynamoTableName [counters]:
Parameter DynamoKeyName [like_counter]:
Parameter Auth0Domain [dev-kq7mz3sahxev7c5d.us.auth0.com]:
Parameter OIDCClientId [TMBk4DmTl8CQP2OLudi5khG2zU6hJoCz]:
Parameter GoldMemberRoleId []: rol_kWCbvh1FQcU8RiAK
Parameter M2MClientId []: S2p4XrphEXwIUaugwe1lqe1idnqA4JRb
Parameter M2MClientSecret []: lOO-H9RCpjVJxRGeISYM9pui3o6tsbNKQfb15Ls2KCRAVvrrGk5_oW_szNP3Ot4k
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]:
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]:
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]:
Save arguments to configuration file [Y/n]:
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
動作確認
フロントアプリを立ち上げて「ゴールド会員を止める」します。
画面は変わりませんが、consoleを見るとリクエスト成功のログを確認出来ます。
リロードすると「ゴールド会員になる」に変わっています。
Auth0でユーザーのRoleを確認すると、先ほど手動で付けたRoleが無くなっていることを確認出来ます。
フロントの画面で「ゴールド会員になる」と、Roleが再度現れます。
実装は以上になります。
ここまで書いていながら、私はAuth0をまだ触り始めたばかりです。
とは言え、一旦、自分なりにやりたいと思っていたことが出来る状態になったかな?と思うので、アウトプットとして残したい。そんなモチベーションで書きました!
また、本記事の内容を実演したハンズオン動画も作成しました!
(※Youtubeに飛びます)
どの画面を操作したら良いか分からないなど、確認しやすいかと思います。
記事とあわせてご活用ください!
ここまで読んでいただきありがとうございましたー!