UNPKG

@tidecloak/verify

Version:

A lightweight utility for server-side verification of TideCloak-issued JSON Web Tokens (JWTs).

191 lines (144 loc) 6.46 kB
# TideCloak Verify SDK 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. --- ## Installation ```bash npm install @tidecloak/verify # or yarn add @tidecloak/verify ``` --- ## Import ```ts import { verifyTideCloakToken } from '@tidecloak/verify'; ``` --- ## API ### `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). #### Under the hood Internally, `verifyTideCloakToken` uses the [jose](https://github.com/panva/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`. --- ## Examples ### 1. Plain JavaScript (Express) > **ESM / `import` syntax** (add `{ "type": "module" }` in your `package.json`): ```js // 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): ````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 ```jsx // 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) ```ts // 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) ```ts // 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}` }); } ``` --- ## TypeScript Definitions ```ts 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>; ``` ---