この記事は Auth0 AWS Amplify Gen2: OIDC Authorization for AppSync via Identity Pool Federation の記事の日本語訳です。
はじめに
AWS Amplify を使うと、 Amazon Cognito User Pool をベースとした認証機能をシンプルに導入することができます。
しかし、その他のIdPを使う必要がある場面もあります。(組織の方針、すでに構築済みのシステムへの AWS Amplify 組み込み、など。)
この場合、 Amazon Cognito User Pool のフェデレーテッドサインインではなく、 Amazon Cognito Identity Pool の外部 ID プロバイダー方式を採用することができます。
例えば、 Okta 社の Auth0 をすでに採用しているサービスであれば、ログインに Auth0 の SDK を採用していたり、ログイン画面も Auth0 の Universal Login を採用していると思います。 これを活かしたまま、さらに AWS Amplify で追加したバックエンドリソース (AWS AppSync や Amazon DynamoDB) を認証情報を利用してアクセスすることができるようになります。
この記事では、 Auth0 と Cognito Identity Pool Federation の統合により、 AWS Amplify の認証に Auth0 を利用しながら、Amplify Data (AWS AppSync / Amazon DynamoDB) を Amplify のライブラリで利用できるようにするための方法を説明していきます。
公式ドキュメントでは不十分
AWS Amplify には Auth0 導入のためのドキュメントがあります。
https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#federate-with-auth0
しかし、このコード例だけでは十分に実装を進めることができません。
必要な情報
公式ドキュメントにリンクや関連情報あり
- Identity Pool Federation
- ページ内にコードはあるが、ファイル配置方法などの説明が無い。
- Custom Token providers
- ページ内にコードはあるが、ファイル配置方法などの説明が無い。
- AppSync(Data)をOIDC認可で使う場合の設定
- Customize your auth rules / Amplify Docs
- Use OpenID Connect as an authorization provider / Amplify Docs
- 設定可能なことは記載されているが設定ページへのリンクが無く、分断されている。
- Auth0側の具体的な設定手順
- Integrate with Amazon Cognito / Auth0
公式ドキュメントにリンクや具体的記述なし
@auth0/auth0-reactとの統合方法- Amplify Gen2(CDK)で OIDC プロバイダー定義
- class OpenIdConnectProvider (construct) / AWS CDK
- 環境変数の管理方法
backend.tsに環境変数を利用できるが、それに関する情報が無い。
credentialsProvider と tokenProvider
credentialsProvider: Cognito Identity Pool経由でAWS一時認証情報を取得する (Identity Pool Federation)tokenProvider: AppSyncのOIDC認可でIDトークンを渡す (Custom Token providers)
Amplify.configure に設定値として渡す必要があります。
公式ドキュメントでは別々のセクションで説明されていますが、Auth0 + AppSync構成では両方必要です。
具体的には、下記のような形で Amplify.configure に両方を設定しなければいけません。
// Amplify.configure への設定例。詳細は別途記載。
Amplify.configure(outputs, {
Auth: {
credentialsProvider: customCredentialsProvider,
tokenProvider: myTokenProvider,
},
});
backend.ts でのOIDCプロバイダー定義
OIDC プロバイダーの登録手順は、 Auth0 側のドキュメント Integrate with Amazon Cognito に記載があります。
OIDC プロバイダーの使い方については Use OpenID Connect as an authorization provider に言及があります。
しかし、 AWS Amplify Gen2 は AWS CDK と統合されており、カスタムリソースとして Amplify プロジェクト内でリソースの管理を行う方が自然です。 この点の記述が、公式ドキュメントにはありません。
Auth0ドメインの形式の違い
- フロントエンド(
@auth0/auth0-react):dev-xxx.us.auth0.com(https://なし) - バックエンド(IAM OIDCプロバイダー):
https://dev-xxx.us.auth0.com(https://あり) - Cognito Identity Pool の Logins キー:
dev-xxx.us.auth0.com(https://なし)
“URL” なのか “ドメイン” なのか、どこでどちらを使うかを明確に知っておく必要があります。
実装手順
Step 1: Auth0側の設定
- Auth0 Dashboard → Applications → Applications でアプリケーションを作成(Single Page Application)
- 以下を記録:
- Domain(例:
dev-xxx.us.auth0.com) - Client ID
- Domain(例:
- Settings → Allowed Callback URLs に
http://localhost:5173を追加 - Settings → Allowed Logout URLs に
http://localhost:5173を追加 - Settings → Allowed Web Origins に
http://localhost:5173を追加 - Advanced Settings → OAuth → JSON Web Token (JWT) Signature Algorithm が RS256 であることを確認
Step 2: Amplifyプロジェクトのセットアップ
npm create amplify@latest
cd your-project
npm install @auth0/auth0-react @aws-sdk/client-cognito-identity
Step 3: 環境変数の設定
.env ファイルを作成:
# フロントエンド用(Viteで使うためVITE_プレフィックス)
VITE_AUTH0_DOMAIN=dev-xxx.us.auth0.com
VITE_AUTH0_CLIENT_ID=your-auth0-client-id
# バックエンド用(OIDCプロバイダー作成に使用、https://付き)
AUTH0_DOMAIN=https://dev-xxx.us.auth0.com
AUTH0_CLIENT_ID=your-auth0-client-id
注意: VITE_AUTH0_DOMAIN は https:// なし、AUTH0_DOMAIN は https:// ありです。
Step 4: Amplifyバックエンド設定
amplify/auth/resource.ts
Auth0をIdentity Pool Federationで使う場合、defineAuth の externalProviders は使いません。通常のemail認証だけ定義します。
import { defineAuth } from "@aws-amplify/backend";
export const auth = defineAuth({
loginWith: {
email: true,
},
});
amplify/data/resource.ts
AppSyncのデフォルト認可モードをOIDCに設定します。
注意: Auth0 の認証には auth_time が含まれないため、 tokenExpiryFromAuthInSeconds を0として AppSync の検証をスキップさせないと 401 エラーとなります。
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
Todo: a
.model({
content: a.string(),
})
.authorization((allow) => [allow.owner("oidc").identityClaim("sub")]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "oidc",
oidcAuthorizationMode: {
oidcProviderName: "Auth0",
oidcIssuerUrl: "https://dev-xxx.us.auth0.com",
clientId: "your-auth0-client-id",
tokenExpiryFromAuthInSeconds: 0, // Auth0 の認証には auth_time が含まれないため、ここを0として AppSync の検証をスキップさせる必要あり。
tokenExpireFromIssueInSeconds: 3600,
},
},
});
amplify/backend.ts(公式ドキュメントにない重要な部分)
CDKレベルでIAM OIDCプロバイダーを作成し、Identity Poolに紐づけます。dotenv パッケージで .env から環境変数を読み込みます。
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
import * as iam from "aws-cdk-lib/aws-iam";
import { config } from "dotenv";
config();
const backend = defineBackend({
auth,
data,
});
const auth0Domain = process.env.AUTH0_DOMAIN;
const auth0ClientId = process.env.AUTH0_CLIENT_ID;
if (!auth0Domain || !auth0ClientId) {
throw new Error(
"AUTH0_DOMAIN and AUTH0_CLIENT_ID must be set in environment variables",
);
}
// IAM OIDCプロバイダーを作成
const oidcProvider = new iam.OpenIdConnectProvider(
backend.auth.resources.cfnResources.cfnIdentityPool.stack,
"Auth0OIDCProvider",
{
url: auth0Domain, // https://dev-xxx.us.auth0.com
clientIds: [auth0ClientId],
},
);
// Identity PoolにAuth0プロバイダーを追加
const identityPool = backend.auth.resources.cfnResources.cfnIdentityPool;
identityPool.openIdConnectProviderArns = [
...(identityPool.openIdConnectProviderArns || []),
oidcProvider.openIdConnectProviderArn,
];
ポイント: backend.auth.resources.cfnResources.cfnIdentityPool でAmplifyが作成したIdentity PoolのL1コンストラクトに直接アクセスしています。これはAmplify Gen2のエスケープハッチ機能です。
Step 5: カスタム認証情報プロバイダー(フロントエンド)
src/CustomCredentialsProvider.ts を作成します。公式ドキュメントのサンプルを拡張し、tokenProvider も同時に設定します。
import { Amplify } from "aws-amplify";
import {
CredentialsAndIdentityIdProvider,
CredentialsAndIdentityId,
GetCredentialsOptions,
TokenProvider,
decodeJWT,
} from "aws-amplify/auth";
import { CognitoIdentity } from "@aws-sdk/client-cognito-identity";
import outputs from "../amplify_outputs.json";
const cognitoidentity = new CognitoIdentity({
region: outputs.auth.aws_region,
});
class CustomCredentialsProvider implements CredentialsAndIdentityIdProvider {
federatedLogin?: {
domain: string;
token: string;
};
loadFederatedLogin(login?: typeof this.federatedLogin) {
this.federatedLogin = login;
}
async getCredentialsAndIdentityId(
_getCredentialsOptions: GetCredentialsOptions,
): Promise<CredentialsAndIdentityId | undefined> {
try {
if (!this.federatedLogin) {
return undefined;
}
const getIdResult = await cognitoidentity.getId({
IdentityPoolId: outputs.auth.identity_pool_id,
Logins: { [this.federatedLogin.domain]: this.federatedLogin.token },
});
if (!getIdResult.IdentityId) {
throw new Error("Failed to get Identity ID");
}
const cognitoCredentialsResult =
await cognitoidentity.getCredentialsForIdentity({
IdentityId: getIdResult.IdentityId,
Logins: { [this.federatedLogin.domain]: this.federatedLogin.token },
});
if (!cognitoCredentialsResult.Credentials) {
throw new Error("Failed to get credentials");
}
return {
credentials: {
accessKeyId: cognitoCredentialsResult.Credentials.AccessKeyId!,
secretAccessKey: cognitoCredentialsResult.Credentials.SecretKey!,
sessionToken: cognitoCredentialsResult.Credentials.SessionToken,
expiration: cognitoCredentialsResult.Credentials.Expiration,
},
identityId: getIdResult.IdentityId,
};
} catch (e) {
console.log("Error getting credentials: ", e);
return undefined;
}
}
clearCredentialsAndIdentityId(): void {
this.federatedLogin = undefined;
}
}
// AppSync OIDC認可用のカスタムトークンプロバイダー
// ※公式ドキュメントでは credentialsProvider と別セクションだが、
// Auth0 + AppSync構成では両方必要
const myTokenProvider: TokenProvider = {
async getTokens() {
const tokenString = sessionStorage.getItem("auth0_id_token");
if (!tokenString) {
return null;
}
return {
accessToken: decodeJWT(tokenString),
idToken: decodeJWT(tokenString),
};
},
};
const customCredentialsProvider = new CustomCredentialsProvider();
// credentialsProvider と tokenProvider を同時に設定
Amplify.configure(outputs, {
Auth: {
credentialsProvider: customCredentialsProvider,
tokenProvider: myTokenProvider,
},
});
export default customCredentialsProvider;
公式ドキュメントにない重要ポイント:
tokenProviderはAppSyncのOIDC認可で必要。credentialsProviderだけではAppSyncにトークンが渡らない- IDトークンを
sessionStorageに保存し、tokenProviderから参照する accessTokenとidTokenの両方にAuth0のIDトークンを設定する(Auth0のSPAフローではアクセストークンがJWT形式でない場合があるため)
Step 6: Auth0 React SDKの統合
src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./CustomCredentialsProvider"; // 先にインポートしてAmplify.configureを実行
import { Auth0Provider } from "@auth0/auth0-react";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Auth0Provider
domain={import.meta.env.VITE_AUTH0_DOMAIN}
clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
}}
>
<App />
</Auth0Provider>
</React.StrictMode>
);
注意: import "./CustomCredentialsProvider" を App より前にインポートして、Amplify.configure が先に実行されるようにします。
src/App.tsx(Auth0ログイン後のトークン連携)
import { useAuth0 } from "@auth0/auth0-react";
import { useEffect, useState } from "react";
import customCredentialsProvider from "./CustomCredentialsProvider";
function App() {
const { isAuthenticated, getIdTokenClaims } = useAuth0();
const [isTokenReady, setIsTokenReady] = useState(false);
useEffect(() => {
const loadCredentials = async () => {
if (isAuthenticated) {
const tokenClaims = await getIdTokenClaims();
if (tokenClaims?.__raw) {
// Auth0のIDトークンをカスタムプロバイダーに渡す
customCredentialsProvider.loadFederatedLogin({
domain: import.meta.env.VITE_AUTH0_DOMAIN,
token: tokenClaims.__raw,
});
// AppSync OIDC用にsessionStorageにも保存
sessionStorage.setItem("auth0_id_token", tokenClaims.__raw);
setIsTokenReady(true);
}
} else {
setIsTokenReady(false);
customCredentialsProvider.clearCredentialsAndIdentityId();
sessionStorage.removeItem("auth0_id_token");
}
};
loadCredentials();
}, [isAuthenticated, getIdTokenClaims]);
// isTokenReady が true になってからAppSyncクエリを実行する
// ...
}
公式ドキュメントにない重要ポイント:
getIdTokenClaims().__rawでJWT文字列を取得する(__rawプロパティは公式ドキュメントに明記されていない)loadFederatedLoginのdomainにはフロントエンド用のドメイン(https://なし)を渡すisTokenReadyステートでトークン設定完了を管理し、AppSyncクエリの実行タイミングを制御する
Step 7: AppSyncクエリの実行
import { generateClient } from "aws-amplify/api";
import type { Schema } from "../amplify/data/resource";
const client = generateClient<Schema>();
// authMode: "oidc" を明示的に指定
const { data } = await client.models.Todo.list({ authMode: "oidc" });
// リアルタイムサブスクリプションも同様
client.models.Todo.observeQuery({ authMode: "oidc" }).subscribe({
next: (data) => console.log(data.items),
});
Step 8: デプロイ
# サンドボックス環境
npx ampx sandbox
# 本番デプロイ(Amplify Hosting使用時は環境変数をコンソールで設定)
Amplify Hostingを使う場合、AUTH0_DOMAIN と AUTH0_CLIENT_ID はAmplifyコンソールの環境変数に設定します。VITE_ プレフィックスの変数はビルド時に埋め込まれるため、同様にビルド環境変数として設定が必要です。
トラブルシューティング
Invalid login token エラー
- Auth0のJWT署名アルゴリズムがRS256か確認
Loginsキーのドメイン形式が正しいか確認(https://の有無)- Auth0のClient IDがIAM OIDCプロバイダーのAudienceと一致しているか確認
No credentials / undefined credentials
loadFederatedLoginが呼ばれる前にAppSyncクエリを実行していないか確認isTokenReadyステートでタイミングを制御する
AppSyncで Unauthorized エラー
tokenProviderが設定されているか確認(credentialsProviderだけでは不十分)data/resource.tsのoidcIssuerUrlがAuth0ドメインと一致しているか確認authMode: "oidc"をクエリに明示的に指定しているか確認
Identity Pool で NotAuthorizedException
- IAM OIDCプロバイダーのURLとAudienceが正しいか確認
- Identity Poolの認証プロバイダー設定にAuth0が含まれているか確認
まとめ
公式ドキュメントでは「カスタム認証情報プロバイダーを作ってAuth0のトークンを渡す」という大枠しか説明されていません。実際に動かすには以下の追加作業が必要です:
- CDKレベルでのOIDCプロバイダー定義(
backend.ts) tokenProviderの実装(AppSync OIDC認可に必須)- Auth0 React SDKとの統合コード(
useAuth0フックからのトークン取得・連携) - 環境変数のドメイン形式の使い分け(
https://の有無) - トークン設定完了のタイミング管理(
isTokenReadyパターン)
他のOIDCプロバイダーへの応用
本記事ではAuth0を例にしましたが、このIdentity Pool FederationパターンはOIDC準拠のプロバイダーであれば同じ構成で適用可能です。Okta、Google Workspace、Microsoft Entra ID(旧 Azure AD)、Keycloakなど、IDトークン(JWT/RS256)を発行できるプロバイダーであれば、以下の箇所を差し替えるだけで対応できます:
- フロントエンドの認証SDK(例:
@okta/okta-react、@azure/msal-reactなど) loadFederatedLoginに渡すdomain(プロバイダーのissuer URL)backend.tsの IAM OIDCプロバイダーURLdata/resource.tsのoidcIssuerUrlとclientId
カスタム credentialsProvider / tokenProvider の実装パターンや、CDKでのIdentity Pool設定はそのまま流用できます。