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.