Tutorial: Integrate TideCloak into an Existing React.js app
This tutorial walks you through setting up the TideCloak SDK in a React application to handle secure authentication.
1) Prerequisites
Before you begin, ensure you have the following:
- React 18 or later
- Node.js ≥18.17.0
- A running TideCloak server you have admin control over
- QEA (Quorum Enforced Authorization) enabled realm
- A registered client in your realm with default user contexts approved and committed
- A valid TideCloak adapter JSON file, saved as
tidecloak.jsonin your project root - An existing React app. The commands below assume it was created with Vite and runs at
http://localhost:5173
In the tide-console, open Apps → your client, set Valid redirect URIs to http://localhost:5173/* and Web origins to http://localhost:5173 (use your app's URL if it differs), then go to Credentials → Tide adapter → Download tidecloak.json and save it in your project root.
With QEA enabled, the redirect URI and web origin edits are filed as change requests. Approve them under QEA Approvals before you download the adapter; they take effect only once committed.
2) Install @tidecloak/react
Add the TideCloak React SDK to your project:
- NPM
- Yarn
npm install @tidecloak/react
yarn add @tidecloak/react
This bundle provides:
<TideCloakContextProvider>- application-level contextuseTideCloak()hook - access tokens and auth actions<Authenticated>/<Unauthenticated>- UI guardsdoEncrypt()/doDecrypt()- tag-based encryption/decryption
Note: Installing this package automatically adds a
silent-check-sso.htmlfile to yourpublicdirectory. This file is required for silent SSO checks; if it doesn't exist, create it manually atpublic/silent-check-sso.htmlwith the following content, otherwise the app will break:public/silent-check-sso.html<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>
3) Initialize the Provider
Wrap your app's root with <TideCloakContextProvider> to enable authentication context throughout the component tree.
This example uses React Router. If your app doesn't have it yet, install it:
npm install react-router-dom
Create a home page that shows the sign-in state:
import { useTideCloak, Authenticated, Unauthenticated } from '@tidecloak/react';export default function Home() {const { login, logout, getValueFromIdToken } = useTideCloak();return (<main><Authenticated><p>Signed in as <b>{getValueFromIdToken('preferred_username')}</b></p><button onClick={logout}>Log Out</button></Authenticated><Unauthenticated><p>You are not signed in.</p><button onClick={login}>Log In</button></Unauthenticated></main>);}
Then wrap the router with the provider:
import { BrowserRouter, Routes, Route } from 'react-router-dom';import { TideCloakContextProvider } from '@tidecloak/react';import adapter from '../tidecloak.json';import Home from './pages/Home';import RedirectPage from './pages/auth/RedirectPage';export default function App() {return (<TideCloakContextProvider config={adapter}><BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/auth/redirect" element={<RedirectPage />} />{/* Add additional routes here */}</Routes></BrowserRouter></TideCloakContextProvider>);}
If you don't pass
config, the provider fetches its configuration fromconfigUrl, which defaults to/adapter.json. This tutorial importstidecloak.jsondirectly instead.
⚠️ If you don't define a route at
/auth/redirect, and you're using the defaultredirectUri, your app will break after login/logout. Either create this route or overrideredirectUriin the provider config.If you override the
redirectUri, you must ensure that the custom path exists in your router. Otherwise, the app will redirect to a non-existent route and fail.
4) Redirect URI Handling
TideCloak supports an optional redirectUri config field. This defines where the user is sent after login/logout.
If omitted, it defaults to:
`${window.location.origin}/auth/redirect`
Example: If your app runs at
http://localhost:3000, then by default the redirect path ishttp://localhost:3000/auth/redirect.
You must create this route if you use the default, or explicitly override it:
<TideCloakContextProvider config={{ ...adapter, redirectUri: 'https://yourapp.com/auth/callback' }}><YourApp /></TideCloakContextProvider>
⚠️ If you override the
redirectUri, make sure the specified path exists in your app. Missing this route will cause failed redirects.
Example: Redirect Handling Page
import { useEffect } from 'react';import { useNavigate } from 'react-router-dom';import { useTideCloak } from '@tidecloak/react';export default function RedirectPage() {const { authenticated, isInitializing, logout } = useTideCloak();const navigate = useNavigate();useEffect(() => {const params = new URLSearchParams(window.location.search);if (params.get("auth") === "failed") {sessionStorage.setItem("tokenExpired", "true");logout();}}, []);useEffect(() => {if (!isInitializing) {navigate('/', { replace: true });}}, [authenticated, isInitializing, navigate]);return (<div style={{minHeight: '100vh',display: 'flex',alignItems: 'center',justifyContent: 'center',fontSize: '1rem',color: '#555',}}><p>Waiting for authentication...</p></div>);}
Description: This page helps finalize the login or logout flow, and also reacts to token expiration events that may have triggered a redirect. It's required if you're using the default redirectUri. If you override the redirect URI, the file is optional-but the route for the redirect must still exist in your app.
5) Using the useTideCloak Hook
Use this hook anywhere in your component tree to manage authentication:
import { useTideCloak } from '@tidecloak/react';function Header() {const {authenticated,login,logout,token,tokenExp,refreshToken,getValueFromToken,getValueFromIdToken,hasRealmRole,hasClientRole,doEncrypt,doDecrypt,} = useTideCloak();return (<header>{authenticated ? (<><span>Logged in</span><button onClick={logout}>Log Out</button></>) : (<button onClick={login}>Log In</button>)}{token && tokenExp !== null && (<small>Expires at {new Date(tokenExp * 1000).toLocaleTimeString()}</small>)}</header>);}
| Name | Type | Description |
|---|---|---|
authenticated | boolean | Whether the user is logged in. |
login() / logout() | () => Promise<void> | Trigger the login or logout flows. |
token, tokenExp | string | null, number | null | Access token and its expiration timestamp (null until signed in). |
| Automatic token refresh | built-in | Tokens refresh silently on expiration-no manual setup needed. |
refreshToken() | () => Promise<boolean> | Force a silent token renewal. |
getValueFromToken(key) | (key: string) => any | Read a custom claim from the access token. |
getValueFromIdToken(key) | (key: string) => any | Read a custom claim from the ID token. |
hasRealmRole(role) | (role: string) => boolean | Check a realm-level role. |
hasClientRole(role, client?) | (role: string, client?: string) => boolean | Check a client-level role; defaults to your app's client ID if omitted. |
doEncrypt(data) / doDecrypt(data) | (data: any) => Promise<any> | Encrypt or decrypt payloads via TideCloak's built-in service. |
6) Guard Components
Use these components to show or hide content based on authentication state:
import { Authenticated, Unauthenticated } from '@tidecloak/react';function Dashboard() {return (<><Authenticated><h1>Dashboard</h1>{/* Protected widgets */}</Authenticated><Unauthenticated><p>Please log in to access the dashboard.</p></Unauthenticated></>);}
<Authenticated>: renders children only whenauthenticated === true<Unauthenticated>: renders children only whenauthenticated === false
7) Encrypting & Decrypting Data
Protect sensitive payloads using tag-based encryption/decryption:
// Encrypt:const encryptedArray = await doEncrypt([{ data: 'user@example.com', tags: ['email'] },]);// Decrypt:const decryptedArray = await doDecrypt([{ encrypted: encryptedArray[0], tags: ['email'] },]);
Important: The
dataproperty must be either a string or aUint8Array(raw bytes). When you encrypt a string, decryption returns a string. When you encrypt aUint8Array, decryption returns aUint8Array.
Valid example:
// Before testing below, ensure you've set up the necessary roles:const multi_encrypted_addresses = await doEncrypt([{data: "10 Smith Street",tags: ["street"]},{data: "Southport",tags: ["suburb"]},{data: "20 James Street - Burleigh Heads",tags: ["street", "suburb"]}]);
Invalid (will fail):
// Prepare data for encryptionconst dataToEncrypt = {title: noteData.title,content: noteData.content};// Encrypt the note data using TideCloak (this will error)const encryptedArray = await doEncrypt([{ data: dataToEncrypt, tags: ['note'] }]);
Permissions: Encryption requires
_tide_<tag>.selfencrypt; decryption requires_tide_<tag>.selfdecrypt.Order guarantee: Output preserves input order.
8) Advanced & Best Practices
- Auto-Refresh: built-in, no manual timer setup required
- Error Handling: check
initErrorfromuseTideCloak - Custom Claims: access token fields with
getValueFromToken()/getValueFromIdToken() - Role-Based UI: combine
hasRealmRole,hasClientRole, and guard components - Lazy Initialization: optionally wrap
<TideCloakContextProvider>around only protected routes
✅ Run & Test
Start your dev server (npm start if your app uses Create React App):
npm run dev
- Open
http://localhost:5173. You should see You are not signed in. - Click Log In.
- Create an account (or sign in) on the Tide login page.
- You are sent to
/auth/redirectand then back home, which now shows Signed in as your username and a Log Out button. - Click Log Out.
- You are back on the signed-out home page, which shows You are not signed in. and the Log In button again.
Your React app is now connected with TideCloak authentication.