# 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 signature, issuer, authorized party (`azp`), 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 Tidecloak client configuration you download from your realm settings).               |
| `token`        | `string`              | The raw JWT (access token) to verify.                                                                                |
| `allowedRoles` | `string[]` (optional) | Array of Tidecloak 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 Tidecloak.
4. Verify the token's signature against a **pinned algorithm allowlist** (`ES256`, `ES384`, `ES512`, `EdDSA` by default), the `issuer`, and the standard time claims (`exp`/`nbf`) with a small `clockTolerance`.
5. Verify the `azp` (authorized party) against `config.resource` — **only when `resource` is configured**.
6. Extract realm (`payload.realm_access.roles`) and client (`payload.resource_access[resource].roles`) roles.
7. Check for at least one matching role if `allowedRoles` is specified.

On any failure, it logs the error message to the console and returns `null`.

> **Note:** the algorithm allowlist closes algorithm-confusion attacks — without it, `jose` would accept any algorithm a key in the set can validate. A `null` result collapses both invalid tokens and infrastructure failures (e.g. an unreachable remote JWKS endpoint), so treat `null` as "not authorized" and monitor your JWKS reachability separately.

### Optional config fields

You can tune verification by adding these optional fields to the `config` object:

| Field                       | Type                   | Default                                  | Description                                                                 |
| --------------------------- | ---------------------- | ---------------------------------------- | --------------------------------------------------------------------------- |
| `tokenSignatureAlgorithms`  | `string[]`             | `['ES256','ES384','ES512','EdDSA']`      | Allowed JWS signature algorithms. Override if your realm signs with others (e.g. `['RS256']`). |
| `clockTolerance`            | `number` \| `string`   | `'5s'`                                   | Allowed clock skew between issuer and verifier (seconds, or a jose duration string). |

---

## 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; 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>;
```

---

