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:
- Create a new Web application in your ZITADEL project
- Select PKCE as the authentication method
- Configure redirect URIs:
- Development:
http://localhost:3000/auth/callback
- Configure post-logout redirect URIs:
- Development:
http://localhost:3000
- 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:
Configuration
Create a .env file:
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
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>;
}
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:
Open http://localhost:3000 in your browser.
Testing the Flow
- Visit the home page
- Click Sign In with ZITADEL
- Authenticate with your ZITADEL credentials
- You’ll be redirected back to the profile page
- View your user information and claims
- 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