この記事は 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認可で使う場合の設定
  • Auth0側の具体的な設定手順

公式ドキュメントにリンクや具体的記述なし

credentialsProvidertokenProvider

  • 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.comhttps:// なし)
  • バックエンド(IAM OIDCプロバイダー): https://dev-xxx.us.auth0.comhttps:// あり)
  • Cognito Identity Pool の Logins キー: dev-xxx.us.auth0.comhttps:// なし)

“URL” なのか “ドメイン” なのか、どこでどちらを使うかを明確に知っておく必要があります。

実装手順

Step 1: Auth0側の設定

  1. Auth0 Dashboard → Applications → Applications でアプリケーションを作成(Single Page Application)
  2. 以下を記録:
    • Domain(例: dev-xxx.us.auth0.com
    • Client ID
  3. Settings → Allowed Callback URLs に http://localhost:5173 を追加
  4. Settings → Allowed Logout URLs に http://localhost:5173 を追加
  5. Settings → Allowed Web Origins に http://localhost:5173 を追加
  6. 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_DOMAINhttps:// なし、AUTH0_DOMAINhttps:// ありです。

Step 4: Amplifyバックエンド設定

amplify/auth/resource.ts

Auth0をIdentity Pool Federationで使う場合、defineAuthexternalProviders は使いません。通常の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 から参照する
  • accessTokenidToken の両方に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 プロパティは公式ドキュメントに明記されていない)
  • loadFederatedLogindomain にはフロントエンド用のドメイン(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_DOMAINAUTH0_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.tsoidcIssuerUrl がAuth0ドメインと一致しているか確認
  • authMode: "oidc" をクエリに明示的に指定しているか確認

Identity Pool で NotAuthorizedException

  • IAM OIDCプロバイダーのURLとAudienceが正しいか確認
  • Identity Poolの認証プロバイダー設定にAuth0が含まれているか確認

まとめ

公式ドキュメントでは「カスタム認証情報プロバイダーを作ってAuth0のトークンを渡す」という大枠しか説明されていません。実際に動かすには以下の追加作業が必要です:

  1. CDKレベルでのOIDCプロバイダー定義backend.ts
  2. tokenProvider の実装(AppSync OIDC認可に必須)
  3. Auth0 React SDKとの統合コードuseAuth0 フックからのトークン取得・連携)
  4. 環境変数のドメイン形式の使い分けhttps:// の有無)
  5. トークン設定完了のタイミング管理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プロバイダーURL
  • data/resource.tsoidcIssuerUrlclientId

カスタム credentialsProvider / tokenProvider の実装パターンや、CDKでのIdentity Pool設定はそのまま流用できます。

参照ドキュメント一覧

#ドキュメントURL用途
1Amplify - Identity Pool Federationhttps://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#identity-pool-federation-3カスタム認証情報プロバイダーの実装パターン
2Amplify - Federate with Auth0https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#federate-with-auth0Auth0連携の概要(ただし情報が少ない)
3Amplify - Custom Token providershttps://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#custom-token-providersAppSync OIDC認可用のトークンプロバイダー
4Amplify - Customize your auth rulehttps://docs.amplify.aws/react/build-a-backend/data/customize-authz/AppSync の認証ルール
5Amplify - Use OpenID Connect as an authorization providerhttps://docs.amplify.aws/react/build-a-backend/data/customize-authz/using-oidc-authorization-provider/AppSync の OIDC 認可設定
6Auth0 - Integrate with Amazon Cognitohttps://auth0.com/docs/customize/integrations/aws/amazon-cognitoAuth0側の設定手順(IAM OIDCプロバイダー作成等)
7Auth0 React SDKhttps://auth0.com/docs/quickstart/spa/react@auth0/auth0-react の導入
8AWS - Open ID Connect providers (Identity Pools)https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.htmlCognito Identity PoolでのOIDC連携の仕組み