Skip to main content

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
  1. Default realm nextjs-test with Tide IdP set as default.
  2. Optional branding on the Tide login page.
  3. Simplified user-profile and registration off.
  4. Link Tide Account required action enabled.
  5. Client myclient with redirect URIs http://localhost:3000/silent-check-sso.html and http://localhost:3000/auth/redirect; Web origins http://localhost:3000.
  6. JWT includes roles via dedicated scope.
  7. 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!​

  1. Visit http://localhost:3000
  2. Log in
  3. Hit the protected API route and try the E2EE example page
  4. Logout to test Single‑Sign‑Out