Skip to main content

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?)

ParameterTypeDescription
configobjectYour TideCloak adapter JSON (the Keycloak client configuration you download from your realm settings).
tokenstringThe raw JWT (access token) to verify.
allowedRolesstring[] (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:

  1. Ensure a token is present.
  2. Construct the correct issuer URL from config['auth-server-url'] and config.realm.
  3. Choose between a local JWK Set (config.jwk.keys) or fetch the JWK Set remotely from Keycloak.
  4. Verify the token's signature, issuer, and azp (authorized party) against config.resource.
  5. Extract realm (payload.realm_access.roles) and client (payload.resource_access[resource].roles) roles.
  6. 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 your package.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>;