How-to Guide: Common Tasks with TideCloak in Next.js
Common tasks you'll use most with TideCloak in a Next.js app - quick, copy-paste snippets.
π§° How to: Install or Scaffold a Projectβ
Scaffold a ready-to-go template:
sudo apt update && sudo apt install -y curl jq
npm init @tidecloak/nextjs@latest my-app
Install the SDK in an existing app:
npm install @tidecloak/nextjs
# or
yarn add @tidecloak/nextjs
Create public/silent-check-sso.html
(required for silent SSO):
<html>
<body>
<script>parent.postMessage(location.href, location.origin)</script>
</body>
</html>
π§© How to: Initialize the Provider (App & Pages Router)β
App Router (/app/layout.tsx
)
import React from 'react';
import { TideCloakProvider } from '@tidecloak/nextjs';
import adapter from '../tidecloakAdapter.json';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<TideCloakProvider config={{ ...adapter }}>
{children}
</TideCloakProvider>
</body>
</html>
);
}
Pages Router (/pages/_app.tsx
)
import React from 'react';
import { TideCloakProvider } from '@tidecloak/nextjs';
import adapter from '../tidecloakAdapter.json';
function MyApp({ Component, pageProps }) {
return (
<TideCloakProvider config={adapter}>
<Component {...pageProps} />
</TideCloakProvider>
);
}
export default MyApp;
π How to: Customize Redirect URIβ
Specify a custom redirect URI after login/logout:
<TideCloakProvider config={{ ...adapter, redirectUri: 'https://yourdomain.com/auth/callback' }}>
{children}
</TideCloakProvider>
Default (if omitted):
`${window.location.origin}/auth/redirect`
Example redirect page (/app/auth/redirect/page.tsx
)
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useTideCloak } from '@tidecloak/nextjs';
export default function RedirectPage() {
const { authenticated, isInitializing, logout } = useTideCloak();
const router = useRouter();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (params.get("auth") === "failed") {
sessionStorage.setItem("tokenExpired", "true");
logout();
}
}, []);
useEffect(() => {
if (!isInitializing) {
router.push(authenticated ? '/home' : '/');
}
}, [authenticated, isInitializing, router]);
return (
<div style={{ minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center' }}>
<p>Waiting for authentication...</p>
</div>
);
}
Ensure the path exists in your app to avoid redirect issues.
ποΈ How to: Get User Info from Access/ID Tokensβ
Retrieve user information stored in tokens:
const { getValueFromToken, getValueFromIdToken } = useTideCloak();
const userEmail = getValueFromToken('email');
const preferredUsername = getValueFromIdToken('preferred_username');
π How to: Check User Rolesβ
Check realm and client roles:
const { hasRealmRole, hasClientRole } = useTideCloak();
const isAdmin = hasRealmRole('admin');
const isEditor = hasClientRole('editor'); // defaults to your app client
const isBillingInOtherClient = hasClientRole('billing:writer', 'backoffice-client');
β»οΈ How to: Manually Refresh Tokenβ
Force a silent token refresh:
const { refreshToken } = useTideCloak();
const refreshed = await refreshToken();
console.log(refreshed ? 'Token refreshed!' : 'Refresh failed.');
Tokens also refresh automatically when they expire.
π How to: Encrypt Data Before Sendingβ
Encrypt sensitive data using tag-based encryption:
const { doEncrypt } = useTideCloak();
const encrypted = await doEncrypt([
{ data: "Sensitive Information", tags: ["private"] }
]);
Note:
data
must be a string or Uint8Array. Permissions: require_tide_<tag>.selfencrypt
on the access token.
π How to: Decrypt Received Dataβ
Decrypt encrypted payloads:
const { doDecrypt } = useTideCloak();
const decrypted = await doDecrypt([
{ encrypted: receivedEncryptedData, tags: ["private"] }
]);
Permissions: require
_tide_<tag>.selfdecrypt
on the access token. Order guarantee: returned array preserves the input order.
π§± How to: Gate UI with Guard Componentsβ
Render UI conditionally based on auth state:
'use client';
import { Authenticated, Unauthenticated } from '@tidecloak/nextjs';
export default function Dashboard() {
return (
<>
<Authenticated>
<h1>Dashboard</h1>
{/* Protected widgets */}
</Authenticated>
<Unauthenticated>
<p>Please log in to access the dashboard.</p>
</Unauthenticated>
</>
);
}
π§ How to: Protect Routes with Edge Middlewareβ
Add a global middleware to protect pages and APIs:
// /middleware.ts
import { NextResponse } from 'next/server';
import tidecloakConfig from './tidecloakAdapter.json';
import { createTideCloakMiddleware } from '@tidecloak/nextjs/server/tidecloakMiddleware';
export default createTideCloakMiddleware({
config: tidecloakConfig,
protectedRoutes: {
'/admin/*': ['admin'],
'/api/private/*': ['user'],
},
onFailure: ({ token }, req) => NextResponse.redirect(new URL('/login', req.url)),
onError: (err, req) => NextResponse.rewrite(new URL('/error', req.url)),
});
export const config = {
matcher: [
'/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)',
'/api/(.*)',
],
runtime: 'edge',
};
π‘οΈ How to: Verify Tokens in API Routesβ
Pages Router (/pages/api/secure.ts
)
import type { NextApiRequest, NextApiResponse } from 'next';
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
import config from '../../tidecloakAdapter.json';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.cookies.kcToken || req.headers.authorization?.split(' ')[1] || '';
const payload = await verifyTideCloakToken(config, token, ['user']);
if (!payload) return res.status(401).json({ error: 'Unauthorized' });
return res.status(200).json({ data: 'Secure data response' });
}
App Router (/app/api/secure/route.ts
)
import { NextRequest, NextResponse } from 'next/server';
import { verifyTideCloakToken } from '@tidecloak/nextjs/server';
import config from '../../../tidecloakAdapter.json';
export async function GET(req: NextRequest) {
const token = req.cookies.get('kcToken')?.value || '';
const payload = await verifyTideCloakToken(config, token, ['user']);
if (!payload) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
return NextResponse.json({ data: 'Secure data response' });
}
β οΈ How to: Handle Authentication Errorsβ
Handle initialization errors from the provider:
'use client';
import { useEffect } from 'react';
import { useTideCloak } from '@tidecloak/nextjs';
export default function ErrorBoundaryLite() {
const { initError } = useTideCloak();
useEffect(() => {
if (initError) {
console.error('Authentication error:', initError);
alert('Authentication failed. Please log in again.');
}
}, [initError]);
return null;
}
How to: Run TideCloak locally (dev)β
If you don't already have a TideCloak instance, run the dev container which auto-imports a sample realm.
sudo docker run -d -v .:/opt/keycloak/data/h2 -v ./nextjs-test-realm.json:/opt/keycloak/data/import/nextjs-test-realm.json --name tidecloak -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=password tideorg/tidecloak-dev:latest
Admin console: http://localhost:8080
Your realm is automatically imported in the dev image
- Default realm
nextjs-test
with Tide IdP set as default. - Optional branding on the Tide login page.
- Simplified user-profile and registration off.
Link Tide Account
required action enabled.- Client myclient with redirect URIs
http://localhost:3000/silent-check-sso.html
andhttp://localhost:3000/auth/redirect
; Web originshttp://localhost:3000
. - JWT includes roles via dedicated scope.
- Realm roles
_tide_dob.selfdecrypt
and_tide_dob.selfencrypt
added to defaults.
Activate a license (dev/free)β
Go to the realm's Tide IdP Settings β Manage License β Request License and complete the flow. The host becomes licensed/activated (free dev tier available).
Play!β
- Visit
http://localhost:3000
- Log in
- Hit the protected API route and try the E2EE example page
- Logout to test SingleβSignβOut