DEV Community

Cover image for Auth0 AWS Amplify Gen2: OIDC Authorization for AppSync via Identity Pool Federation

Auth0 AWS Amplify Gen2: OIDC Authorization for AppSync via Identity Pool Federation

Introduction

AWS Amplify simplifies adding authentication using Amazon Cognito User Pool.

However, there are situations where you need to use a different IdP — for example, due to organizational policy or when integrating AWS Amplify into an existing system.

In such cases, instead of using federated sign-in through Amazon Cognito User Pool, you can adopt the external ID provider approach via Amazon Cognito Identity Pool.

For instance, if your service already uses Auth0 by Okta, you are likely using the Auth0 SDK for login and Auth0's Universal Login for the sign-in screen.
You can keep this setup while also accessing backend resources added through AWS Amplify (such as AWS AppSync and Amazon DynamoDB) using authenticated credentials.

This article provides a guide on integrating Auth0 with Cognito Identity Pool Federation so that you can use Auth0 for authentication in AWS Amplify while accessing Amplify Data (AWS AppSync / Amazon DynamoDB) through the Amplify libraries.

The Official Documentation Is Not Enough

AWS Amplify has documentation for integrating Auth0:

Advanced workflows - React - AWS Amplify Gen 2 Documentation

Learn more about advanced workflows in the Amplify auth category. This includes subscribing to events, identity pool federation, auth-related Lambda triggers and working with AWS service objects. AWS Amplify Documentation

favicon docs.amplify.aws

However, the code examples alone are not sufficient to complete the implementation.

Required Information

Available in Official Documentation (with links or related info)

  • Identity Pool Federation
    • Code is provided on the page, but there is no explanation of file placement or project structure.
  • Custom Token providers
    • Code is provided on the page, but there is no explanation of file placement or project structure.
  • Configuring OIDC authorization for AppSync (Data)
  • Auth0-side setup steps

Not in Official Documentation (no links or specific details)

credentialsProvider and tokenProvider

  • credentialsProvider: Obtains temporary AWS credentials via Cognito Identity Pool (Identity Pool Federation)
  • tokenProvider: Passes the ID token for AppSync OIDC authorization (Custom Token providers)

Both must be passed to Amplify.configure.
The official documentation explains them in separate sections, but for an Auth0 + AppSync setup, both are required.
Specifically, you need to configure both in Amplify.configure as follows:

// Example Amplify.configure setup. Details described later.
Amplify.configure(outputs, {
  Auth: {
    credentialsProvider: customCredentialsProvider,
    tokenProvider: myTokenProvider,
  },
});
Enter fullscreen mode Exit fullscreen mode

Defining the OIDC Provider in backend.ts

The steps for registering an OIDC provider are documented on the Auth0 side: Integrate with Amazon Cognito.

How to use an OIDC provider is covered in Use OpenID Connect as an authorization provider.

However, since AWS Amplify Gen2 is integrated with AWS CDK, it is more natural to manage resources as custom resources within the Amplify project.
This aspect is not covered in the official documentation.

Auth0 Domain Format Differences

  • Frontend (@auth0/auth0-react): dev-xxx.us.auth0.com (without https://)
  • Backend (IAM OIDC provider): https://dev-xxx.us.auth0.com (with https://)
  • Cognito Identity Pool Logins key: dev-xxx.us.auth0.com (without https://)

You need to clearly understand where to use the "URL" format versus the "domain" format.

Implementation Steps

Step 1: Auth0 Configuration

  1. In Auth0 Dashboard → Applications → Applications, create an application (Single Page Application)
  2. Note the following:
    • Domain (e.g., dev-xxx.us.auth0.com)
    • Client ID
  3. Settings → Add http://localhost:5173 to Allowed Callback URLs
  4. Settings → Add http://localhost:5173 to Allowed Logout URLs
  5. Settings → Add http://localhost:5173 to Allowed Web Origins
  6. Advanced Settings → OAuth → Verify that JSON Web Token (JWT) Signature Algorithm is RS256

Step 2: Amplify Project Setup

npm create amplify@latest
cd your-project
npm install @auth0/auth0-react @aws-sdk/client-cognito-identity
Enter fullscreen mode Exit fullscreen mode

Step 3: Environment Variables

Create a .env file:

# Frontend (VITE_ prefix required for Vite)
VITE_AUTH0_DOMAIN=dev-xxx.us.auth0.com
VITE_AUTH0_CLIENT_ID=your-auth0-client-id

# Backend (used for OIDC provider creation, with https://)
AUTH0_DOMAIN=https://dev-xxx.us.auth0.com
AUTH0_CLIENT_ID=your-auth0-client-id
Enter fullscreen mode Exit fullscreen mode

Note: VITE_AUTH0_DOMAIN does not include https://, while AUTH0_DOMAIN does.

Step 4: Amplify Backend Configuration

amplify/auth/resource.ts

When using Auth0 with Identity Pool Federation, you do not use externalProviders in defineAuth. Just define basic email authentication.

import { defineAuth } from "@aws-amplify/backend";

export const auth = defineAuth({
  loginWith: {
    email: true,
  },
});
Enter fullscreen mode Exit fullscreen mode

amplify/data/resource.ts

Set the default authorization mode for AppSync to OIDC.

Note: Auth0 tokens do not include auth_time, so you must set tokenExpiryFromAuthInSeconds to 0 to skip AppSync's validation. Otherwise, you will get a 401 error.

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 tokens don't include auth_time, so set to 0 to skip AppSync validation.
      tokenExpireFromIssueInSeconds: 3600,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

amplify/backend.ts (Critical part not in official documentation)

Create an IAM OIDC provider at the CDK level and associate it with the Identity Pool. Use the dotenv package to load environment variables from .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",
  );
}

// Create IAM OIDC provider
const oidcProvider = new iam.OpenIdConnectProvider(
  backend.auth.resources.cfnResources.cfnIdentityPool.stack,
  "Auth0OIDCProvider",
  {
    url: auth0Domain, // https://dev-xxx.us.auth0.com
    clientIds: [auth0ClientId],
  },
);

// Add Auth0 provider to Identity Pool
const identityPool = backend.auth.resources.cfnResources.cfnIdentityPool;

identityPool.openIdConnectProviderArns = [
  ...(identityPool.openIdConnectProviderArns || []),
  oidcProvider.openIdConnectProviderArn,
];
Enter fullscreen mode Exit fullscreen mode

Key point: backend.auth.resources.cfnResources.cfnIdentityPool gives you direct access to the L1 construct of the Identity Pool created by Amplify. This is the Amplify Gen2 escape hatch feature.

Step 5: Custom Credentials Provider (Frontend)

Create src/CustomCredentialsProvider.ts. This extends the official documentation sample and configures both tokenProvider and credentialsProvider together.

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;
  }
}

// Custom token provider for AppSync OIDC authorization
// Note: The official docs explain credentialsProvider and tokenProvider in separate sections,
//       but both are required for Auth0 + AppSync setups.
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();

// Configure both credentialsProvider and tokenProvider
Amplify.configure(outputs, {
  Auth: {
    credentialsProvider: customCredentialsProvider,
    tokenProvider: myTokenProvider,
  },
});

export default customCredentialsProvider;
Enter fullscreen mode Exit fullscreen mode

Important points not in official documentation:

  • tokenProvider is required for AppSync OIDC authorization. credentialsProvider alone does not pass tokens to AppSync.
  • The ID token is stored in sessionStorage and referenced by tokenProvider.
  • Both accessToken and idToken are set to Auth0's ID token (because Auth0's SPA flow may not issue the access token in JWT format).

Step 6: Auth0 React SDK Integration

src/main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./CustomCredentialsProvider"; // Import first to execute 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>
);
Enter fullscreen mode Exit fullscreen mode

Note: Import "./CustomCredentialsProvider" before App so that Amplify.configure runs first.

src/App.tsx (Token handoff after Auth0 login)

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) {
          // Pass Auth0 ID token to custom provider
          customCredentialsProvider.loadFederatedLogin({
            domain: import.meta.env.VITE_AUTH0_DOMAIN,
            token: tokenClaims.__raw,
          });
          // Also store in sessionStorage for AppSync OIDC
          sessionStorage.setItem("auth0_id_token", tokenClaims.__raw);
          setIsTokenReady(true);
        }
      } else {
        setIsTokenReady(false);
        customCredentialsProvider.clearCredentialsAndIdentityId();
        sessionStorage.removeItem("auth0_id_token");
      }
    };
    loadCredentials();
  }, [isAuthenticated, getIdTokenClaims]);

  // Execute AppSync queries only after isTokenReady is true
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Important points not in official documentation:

  • Use getIdTokenClaims().__raw to get the JWT string (the __raw property is not explicitly documented)
  • Pass the frontend domain (without https://) to loadFederatedLogin's domain
  • Use the isTokenReady state to control the timing of AppSync query execution

Step 7: Executing AppSync Queries

import { generateClient } from "aws-amplify/api";
import type { Schema } from "../amplify/data/resource";

const client = generateClient<Schema>();

// Explicitly specify authMode: "oidc"
const { data } = await client.models.Todo.list({ authMode: "oidc" });

// Real-time subscriptions work the same way
client.models.Todo.observeQuery({ authMode: "oidc" }).subscribe({
  next: (data) => console.log(data.items),
});
Enter fullscreen mode Exit fullscreen mode

Step 8: Deployment

# Sandbox environment
npx ampx sandbox

# Production deployment (set environment variables in the console when using Amplify Hosting)
Enter fullscreen mode Exit fullscreen mode

When using Amplify Hosting, set AUTH0_DOMAIN and AUTH0_CLIENT_ID as environment variables in the Amplify console. Variables with the VITE_ prefix are embedded at build time, so they also need to be set as build environment variables.


Troubleshooting

Invalid login token Error

  • Verify that Auth0's JWT signature algorithm is RS256
  • Check that the Logins key domain format is correct (with or without https://)
  • Confirm that the Auth0 Client ID matches the IAM OIDC provider's Audience

No credentials / undefined credentials

  • Make sure loadFederatedLogin is called before executing AppSync queries
  • Use the isTokenReady state to control timing

Unauthorized Error in AppSync

  • Verify that tokenProvider is configured (credentialsProvider alone is not enough)
  • Check that oidcIssuerUrl in data/resource.ts matches the Auth0 domain
  • Explicitly specify authMode: "oidc" in queries

NotAuthorizedException in Identity Pool

  • Verify that the IAM OIDC provider URL and Audience are correct
  • Confirm that Auth0 is included in the Identity Pool's authentication provider settings

Summary

The official documentation only explains the general approach of "creating a custom credentials provider and passing Auth0 tokens." To actually get it working, the following additional steps are required:

  1. CDK-level OIDC provider definition (backend.ts)
  2. tokenProvider implementation (required for AppSync OIDC authorization)
  3. Auth0 React SDK integration code (token retrieval and handoff from the useAuth0 hook)
  4. Domain format differences for environment variables (with or without https://)
  5. Token readiness timing management (isTokenReady pattern)

Applying to Other OIDC Providers

This article uses Auth0 as an example, but this Identity Pool Federation pattern can be applied to any OIDC-compliant provider. For providers that issue ID tokens (JWT/RS256) — such as Okta, Google Workspace, Microsoft Entra ID (formerly Azure AD), or Keycloak — you only need to replace the following:

  • Frontend authentication SDK (e.g., @okta/okta-react, @azure/msal-react, etc.)
  • The domain passed to loadFederatedLogin (the provider's issuer URL)
  • The IAM OIDC provider URL in backend.ts
  • oidcIssuerUrl and clientId in data/resource.ts

The custom credentialsProvider / tokenProvider implementation pattern and CDK Identity Pool configuration can be reused as-is.

Reference Documentation

# Document URL Purpose
1 Amplify - Identity Pool Federation https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#identity-pool-federation-3 Custom credentials provider implementation pattern
2 Amplify - Federate with Auth0 https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#federate-with-auth0 Auth0 integration overview (limited information)
3 Amplify - Custom Token providers https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/#custom-token-providers Token provider for AppSync OIDC authorization
4 Amplify - Customize your auth rules https://docs.amplify.aws/react/build-a-backend/data/customize-authz/ AppSync authorization rules
5 Amplify - Use OpenID Connect as an authorization provider https://docs.amplify.aws/react/build-a-backend/data/customize-authz/using-oidc-authorization-provider/ AppSync OIDC authorization configuration
6 Auth0 - Integrate with Amazon Cognito https://auth0.com/docs/customize/integrations/aws/amazon-cognito Auth0-side setup (IAM OIDC provider creation, etc.)
7 Auth0 React SDK https://auth0.com/docs/quickstart/spa/react @auth0/auth0-react setup
8 AWS - Open ID Connect providers (Identity Pools) https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html OIDC integration with Cognito Identity Pool

Top comments (0)