Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zitadel/zitadel/llms.txt

Use this file to discover all available pages before exploring further.

React Example

Learn how to integrate ZITADEL authentication into a React single-page application using the secure OAuth 2.0 PKCE flow.

Overview

React is a popular JavaScript library for building user interfaces. This example demonstrates how to add authentication using react-oidc-context, which wraps the oidc-client-ts library.

Auth Library

This example uses react-oidc-context, a React wrapper around oidc-client-ts for OpenID Connect authentication.

What You’ll Build

  • Public landing page with sign-in functionality
  • PKCE authentication flow with ZITADEL
  • Protected routes using React Router
  • Profile page displaying user claims and information
  • Automatic token refresh with offline access
  • Federated logout functionality

Prerequisites

Create a PKCE application in the ZITADEL Console:
  1. Create a new Web application in your ZITADEL project
  2. Select PKCE as the authentication method
  3. Configure redirect URIs:
    • Development: http://localhost:3000/auth/callback
  4. Configure post-logout redirect URIs:
    • Development: http://localhost:3000
  5. Copy your Client ID
Enable Dev Mode in ZITADEL Console when using HTTP URLs for local development. Use HTTPS in production.

Installation

Clone the example repository:
git clone https://github.com/zitadel/example-auth-react.git
cd example-auth-react
Install dependencies:
npm install

Configuration

Create a .env file:
cp .env.example .env
Configure the environment variables:
VITE_ZITADEL_DOMAIN=https://your-instance.zitadel.cloud
VITE_ZITADEL_CLIENT_ID=your-client-id
VITE_ZITADEL_CALLBACK_URL=http://localhost:3000/auth/callback
VITE_ZITADEL_POST_LOGOUT_URL=http://localhost:3000
VITE_POST_LOGIN_URL=/profile
VITE_PORT=3000

Implementation

Configure OIDC Provider

Wrap your app with AuthProvider in src/main.tsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { AuthProvider } from 'react-oidc-context';
import App from './App';
import './index.css';

const oidcConfig = {
  authority: import.meta.env.VITE_ZITADEL_DOMAIN,
  client_id: import.meta.env.VITE_ZITADEL_CLIENT_ID,
  redirect_uri: import.meta.env.VITE_ZITADEL_CALLBACK_URL,
  post_logout_redirect_uri: import.meta.env.VITE_ZITADEL_POST_LOGOUT_URL,
  scope: 'openid profile email offline_access',
  response_type: 'code',
  automaticSilentRenew: true,
  loadUserInfo: true,
};

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <AuthProvider {...oidcConfig}>
      <App />
    </AuthProvider>
  </React.StrictMode>
);

Create Protected Route Component

Create src/components/ProtectedRoute.tsx:
import { useAuth } from 'react-oidc-context';
import { Navigate } from 'react-router-dom';

interface ProtectedRouteProps {
  children: React.ReactNode;
}

export function ProtectedRoute({ children }: ProtectedRouteProps) {
  const auth = useAuth();

  if (auth.isLoading) {
    return <div>Loading...</div>;
  }

  if (!auth.isAuthenticated) {
    return <Navigate to="/" replace />;
  }

  return <>{children}</>;
}

Home Page

Create src/pages/Home.tsx:
import { useAuth } from 'react-oidc-context';
import { useNavigate } from 'react-router-dom';

export function Home() {
  const auth = useAuth();
  const navigate = useNavigate();

  const handleLogin = () => {
    auth.signinRedirect();
  };

  if (auth.isAuthenticated) {
    navigate('/profile');
    return null;
  }

  return (
    <div className="container">
      <h1>Welcome to ZITADEL + React</h1>
      <p>This example demonstrates authentication with ZITADEL using PKCE.</p>
      <button onClick={handleLogin}>Sign In with ZITADEL</button>
    </div>
  );
}

Profile Page

Create src/pages/Profile.tsx:
import { useAuth } from 'react-oidc-context';

export function Profile() {
  const auth = useAuth();

  const handleLogout = () => {
    auth.signoutRedirect();
  };

  return (
    <div className="container">
      <h1>Profile</h1>
      <div className="profile-info">
        <p><strong>Name:</strong> {auth.user?.profile.name}</p>
        <p><strong>Email:</strong> {auth.user?.profile.email}</p>
        <p><strong>User ID:</strong> {auth.user?.profile.sub}</p>
        <p><strong>Email Verified:</strong> {auth.user?.profile.email_verified ? 'Yes' : 'No'}</p>
      </div>
      
      <h2>Access Token</h2>
      <pre>{auth.user?.access_token}</pre>
      
      <button onClick={handleLogout}>Sign Out</button>
    </div>
  );
}

Callback Handler

Create src/pages/Callback.tsx:
import { useEffect } from 'react';
import { useAuth } from 'react-oidc-context';
import { useNavigate } from 'react-router-dom';

export function Callback() {
  const auth = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    if (auth.isAuthenticated) {
      navigate('/profile');
    }
  }, [auth.isAuthenticated, navigate]);

  return <div>Processing authentication...</div>;
}

Configure Routes

Update src/App.tsx:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { Profile } from './pages/Profile';
import { Callback } from './pages/Callback';
import { ProtectedRoute } from './components/ProtectedRoute';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/auth/callback" element={<Callback />} />
        <Route
          path="/profile"
          element={
            <ProtectedRoute>
              <Profile />
            </ProtectedRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Run the Application

Start the development server:
npm run dev
Open http://localhost:3000 in your browser.

Testing the Flow

  1. Visit the home page
  2. Click Sign In with ZITADEL
  3. Authenticate with your ZITADEL credentials
  4. You’ll be redirected back to the profile page
  5. View your user information and claims
  6. Click Sign Out to test logout

Advanced Features

Check Authentication State

import { useAuth } from 'react-oidc-context';

function MyComponent() {
  const auth = useAuth();

  if (auth.isLoading) {
    return <div>Loading...</div>;
  }

  if (auth.error) {
    return <div>Error: {auth.error.message}</div>;
  }

  if (auth.isAuthenticated) {
    return <div>Welcome, {auth.user?.profile.name}!</div>;
  }

  return <button onClick={() => auth.signinRedirect()}>Sign In</button>;
}

Access User Claims

const user = auth.user;

// Standard OIDC claims
const userId = user?.profile.sub;
const email = user?.profile.email;
const name = user?.profile.name;

// Custom claims
const roles = user?.profile['urn:zitadel:iam:org:project:roles'];

Call Protected APIs

const callApi = async () => {
  const token = auth.user?.access_token;
  
  const response = await fetch('https://api.example.com/data', {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });
  
  const data = await response.json();
  return data;
};

Resources

Next Steps