@tidecloak/nextjs
Version:
TideCloak nextjs SDK
93 lines (92 loc) • 3.63 kB
JavaScript
import { NextResponse } from 'next/server';
import { verifyTideCloakToken } from '@tidecloak/verify';
import { normalizePattern, normalizeProtectedRoutes } from './routerMatcher';
const DEFAULTS = {
protectedRoutes: {},
onRequest: undefined,
onSuccess: undefined,
};
/**
* Returns a Next.js Edge Middleware function enforcing TideCloak auth.
*
* Example usage in your `middleware.ts`:
*
* ```ts
* import keycloakConfig from './tidecloak.config.json'
* import { createTideMiddleware } from 'tidecloak-nextjs/server/tidecloakMiddleware'
*
* export default createTideMiddleware({
* config: keycloakConfig,
* publicRoutes: ['/', '/about'],
* protectedRoutes: {
* '/admin/*': ['admin'],
* '/api/private/*': ['user']
* }
* })
*
* export const config = {
* matcher: [
* '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)',
* '/(api|trpc)(.*)'
* ],
* runtime: 'edge'
* }
* ```
*/
export function createTideCloakMiddleware(opts) {
var _a;
const settings = { ...DEFAULTS, ...opts };
// Prepare arrays of test functions for public and protected routes
const publicTests = ((_a = settings.publicRoutes) !== null && _a !== void 0 ? _a : []).map(normalizePattern);
const protectedTests = normalizeProtectedRoutes(settings.protectedRoutes);
return async function middleware(req) {
var _a;
const path = req.nextUrl.pathname;
try {
// Bypass auth entirely for configured public routes
if (publicTests.some(test => test(path, req))) {
return NextResponse.next();
}
// Extract the raw JWT from the specified cookie
const token = ((_a = req.cookies.get("kcToken")) === null || _a === void 0 ? void 0 : _a.value) || null;
// Allow custom logic before auth checks
if (settings.onRequest) {
const result = settings.onRequest({ token }, req);
if (result)
return result;
}
// Iterate protected routes; the first match enforces a role check
for (const { test, roles } of protectedTests) {
if (test(path, req)) {
// Verify signature, issuer, and presence of at least one allowed role
const payload = await verifyTideCloakToken(settings.config, token, roles);
if (!payload) {
// Custom onFailure hook or default redirect
const result = settings.onFailure({ token }, req);
if (result)
return result;
return NextResponse.json({ error: '[TideCloak Middleware] Access forbidden: invalid token' }, { status: 403 });
}
// Custom onSuccess hook if provided
if (settings.onSuccess) {
const result = settings.onSuccess({ payload }, req);
if (result)
return result;
}
// Token valid and role check passed
return NextResponse.next();
}
}
// No protected route matched; continue
return NextResponse.next();
}
catch (err) {
// Handle unexpected errors
if (settings.onError) {
return settings.onError(err, req);
}
console.error("[TideCloak Middleware] ", err);
throw err;
}
};
}