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 install @tidecloak/verify
# or
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:
null
if 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
allowedRoles
is specified.
On any failure, it logs an error to the console and returns null
.
4. Examples
1. Plain JavaScript (Express)
ESM /
import
syntax (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 /
require
syntax (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>;