TideCloak Next.js Verify JWT
A lightweight utility for server‑side verification of TideCloak‑issued JSON Web Tokens (JWTs).
This package exports a single function, verifyTideCloakToken, which you can use in your Next.js API routes, Node.js servers, or any backend to verify the authenticity, issuer, audience, and roles of a JWT issued by your TideCloak realm.
1. Installation
- NPM
- Yarn
npm install @tidecloak/verify
yarn add @tidecloak/verify
2. Import
import { verifyTideCloakToken } from '@tidecloak/verify';
3. API
1. verifyTideCloakToken(config, token, allowedRoles?)
| Parameter | Type | Description |
|---|---|---|
config | object | Your TideCloak adapter JSON (the Keycloak client configuration you download from your realm settings). |
token | string | The raw JWT (access token) to verify. |
allowedRoles | string[] (optional) | Array of Keycloak realm or client roles. If provided, the user must have at least one of these roles in their token. |
Returns:
Promise<object | null>
- Success: Decoded token payload when all checks pass.
- Failure:
nullif verification fails or the user lacks the required role(s).
1. Under the hood
Internally, verifyTideCloakToken uses the jose library to:
- Ensure a token is present.
- Construct the correct issuer URL from
config['auth-server-url']andconfig.realm. - Choose between a local JWK Set (
config.jwk.keys) or fetch the JWK Set remotely from Keycloak. - Verify the token's signature, issuer, and
azp(authorized party) againstconfig.resource. - Extract realm (
payload.realm_access.roles) and client (payload.resource_access[resource].roles) roles. - Check for at least one matching role if
allowedRolesis specified.
On any failure, it logs an error to the console and returns null.
4. Examples
1. Plain JavaScript (Express)
ESM /
importsyntax (add{ "type": "module" }in yourpackage.json):
server.js
import express from 'express';
import cookieParser from 'cookie-parser';
import { verifyTideCloakToken } from '@tidecloak/verify';
import config from './tidecloakAdapter.json';
const app = express();
app.use(cookieParser());
app.get('/secure', async (req, res) => {
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.json({ message: `Hello, ${payload.preferred_username}` });
});
app.listen(3000, () => console.log('Server running on port 3000'));
CommonJS /
requiresyntax (default Node.js):
server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const { verifyTideCloakToken } = require('@tidecloak/verify');
const config = require('./tidecloakAdapter.json');
const app = express();
app.use(cookieParser());
app.get('/secure', async (req, res) => {
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.json({ message: `Hello, ${payload.preferred_username}` });
});
app.listen(3000, () => console.log('Server running on port 3000'));
2. React with Server-Side Rendering
pages/secure.js (Next.js Pages Router)
import React from 'react';
import { verifyTideCloakToken } from '@tidecloak/verify';
import config from '../tidecloakAdapter.json';
export async function getServerSideProps({ req }) {
const token = req.cookies.kcToken || req.headers.authorization?.split(' ')[1] || '';
const payload = await verifyTideCloakToken(config, token, ['user']);
if (!payload) {
return { redirect: { destination: '/login', permanent: false } };
}
return { props: { user: payload.preferred_username } };
}
export default function SecurePage({ user }) {
return <div>Welcome, {user}</div>;
}
3. Next.js Pages Router (API Route)
pages/api/secure.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { verifyTideCloakToken } from '@tidecloak/verify';
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', 'admin']);
if (!payload) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.status(200).json({ message: 'Hello, ' + payload.preferred_username });
}
4. Next.js App Router (API Route)
app/api/secure/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyTideCloakToken } from '@tidecloak/verify';
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({ message: `Welcome, ${payload.preferred_username}` });
}
5. TypeScript Definitions
interface TidecloakConfig {
realm: string;
'auth-server-url': string;
resource: string;
publicClient?: boolean;
confidentialPort?: number;
jwk?: { keys: Array<{ kid: string; kty: string; alg: string; use: string; x: string; crv?: string }> };
[key: string]: unknown;
}
export declare function verifyTideCloakToken(
config: TidecloakConfig,
token: string,
allowedRoles?: string[]
): Promise<Record<string, any> | null>;