Skip to main content

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.json in 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 install @tidecloak/react

This bundle provides:

  • <TideCloakContextProvider> - application-level context
  • useTideCloak() hook - access tokens and auth actions
  • <Authenticated> / <Unauthenticated> - UI guards
  • doEncrypt() / doDecrypt() - tag-based encryption/decryption

Note: Installing this package automatically adds a silent-check-sso.html file to your public directory. This file is required for silent SSO checks; if it doesn't exist, create it manually at public/silent-check-sso.html with 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:

src/pages/Home.tsx
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:

src/App.tsx
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 from configUrl, which defaults to /adapter.json. This tutorial imports tidecloak.json directly instead.

⚠️ If you don't define a route at /auth/redirect, and you're using the default redirectUri, your app will break after login/logout. Either create this route or override redirectUri in 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 is http://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​

src/pages/auth/RedirectPage.tsx
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>
);
}
NameTypeDescription
authenticatedbooleanWhether the user is logged in.
login() / logout()() => Promise<void>Trigger the login or logout flows.
token, tokenExpstring | null, number | nullAccess token and its expiration timestamp (null until signed in).
Automatic token refreshbuilt-inTokens refresh silently on expiration-no manual setup needed.
refreshToken()() => Promise<boolean>Force a silent token renewal.
getValueFromToken(key)(key: string) => anyRead a custom claim from the access token.
getValueFromIdToken(key)(key: string) => anyRead a custom claim from the ID token.
hasRealmRole(role)(role: string) => booleanCheck a realm-level role.
hasClientRole(role, client?)(role: string, client?: string) => booleanCheck 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 when authenticated === true
  • <Unauthenticated>: renders children only when authenticated === 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 data property must be either a string or a Uint8Array (raw bytes). When you encrypt a string, decryption returns a string. When you encrypt a Uint8Array, decryption returns a Uint8Array.

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 encryption
const 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 initError from useTideCloak
  • 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
  1. Open http://localhost:5173. You should see You are not signed in.
  2. Click Log In.
  3. Create an account (or sign in) on the Tide login page.
  4. You are sent to /auth/redirect and then back home, which now shows Signed in as your username and a Log Out button.
  5. Click Log Out.
  6. 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.