UNPKG

@tidecloak/nextjs

Version:
93 lines (92 loc) 3.63 kB
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; } }; }