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).
Tokens must come from a Tide realm with IGA (QEA) enabled. Those tokens are signed with EdDSA. A realm without IGA signs with RS256, which is not in the default list of accepted algorithms, so its tokens are rejected.
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. Accepted signature algorithms default to ES256, ES384, ES512 and EdDSA, and can be changed withconfig.tokenSignatureAlgorithms. - 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)
You need your realm's adapter file, tidecloak.json, next to server.js. Download it from the tide-console → Apps → your app → Credentials → Download tidecloak.json.
Install Express and the cookie parser, and switch the project to ES modules:
npm install express cookie-parsernpm pkg set type=module
ESM /
importsyntax (needs"type": "module"in yourpackage.json, and Node.js 18.20 or 20.10+ for JSON import attributes):
import express from 'express';import cookieParser from 'cookie-parser';import { verifyTideCloakToken } from '@tidecloak/verify';import config from './tidecloak.json' with { type: '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'));
Start the server. A request without a token gets 401:
node server.js
CommonJS /
requiresyntax (default Node.js, without"type": "module"):
const express = require('express');const cookieParser = require('cookie-parser');const { verifyTideCloakToken } = require('@tidecloak/verify');const config = require('./tidecloak.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
This example is a Next.js Pages Router page:
import React from 'react';import { verifyTideCloakToken } from '@tidecloak/verify';import config from '../tidecloak.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)
import type { NextApiRequest, NextApiResponse } from 'next';import { verifyTideCloakToken } from '@tidecloak/verify';import config from '../../tidecloak.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)
import { NextRequest, NextResponse } from 'next/server';import { verifyTideCloakToken } from '@tidecloak/verify';import config from '../../../tidecloak.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; n?: string; e?: string }> };/** Allowed JWS signature algorithms. Default: ['ES256','ES384','ES512','EdDSA']. */tokenSignatureAlgorithms?: string[];/** Allowed clock skew (seconds or a jose duration string). Default: '5s'. */clockTolerance?: number | string;[key: string]: unknown;}export declare function verifyTideCloakToken(config: TidecloakConfig,token: string,allowedRoles?: string[]): Promise<Record<string, any> | null>;