Skip to main content

Tutorial: Integrate TideCloak into an Existing Next.js App

Add TideCloak auth to an existing Next.js app - provider setup, redirect, middleware, API verification, and E2EE.

1) Prepare TideCloak

  • Ensure your realm is set up (you can use the dev container in the other tutorial).
  • In the Admin Console, go to Clients → myclient → Action → Download adaptor configs (format: keycloak-oidc-keycloak-json).
  • Save the JSON in your app (e.g., /tidecloakAdapter.json).

2) Install the SDK

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>

3) Wrap your app with <TideCloakProvider>

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;

4) Redirect URI

If not specified, the SDK assumes:

`${window.location.origin}/auth/redirect`

Create a page at that route (App Router example):

'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 <p style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Waiting for authentication...</p>;
}

Or pass an explicit redirectUri in the provider config.

5) Use the hook & guard components

'use client';
import { useTideCloak, Authenticated, Unauthenticated } from '@tidecloak/nextjs';

function Header() {
const { authenticated, login, logout, tokenExp } = useTideCloak();
return (
<header>
{authenticated ? (
<>
<span>Logged in (exp {new Date(tokenExp * 1000).toLocaleTimeString()})</span>
<button onClick={logout}>Log Out</button>
</>
) : <button onClick={login}>Log In</button>}
</header>
);
}

6) Protect routes with Edge middleware

/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',
};

7) 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' });
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' });
}

8) Optional - E2EE with tag-based encryption

// Encrypt:
const encryptedArray = await doEncrypt([
{ data: "10 Smith Street", tags: ["street"] },
]);

// Decrypt:
const decryptedArray = await doDecrypt([
{ encrypted: encryptedArray[0], tags: ["street"] },
]);
  • Permissions: _tide_<tag>.selfencrypt to encrypt; _tide_<tag>.selfdecrypt to decrypt.
  • Data types: data must be a string or Uint8Array (objects will fail).

9) Try it

  • Run your app, login, hit a protected API, and test SSO logout.