> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JWT

> Generate, sign, verify, and decode JSON Web Tokens with the JOSE-backed Jwt class for stateless authentication.

A **JSON Web Token (JWT)** is a compact, URL-safe string that carries a signed set of claims about a user — who they are, what they can do, and when the token expires. Because the signature proves the token was issued by your server, you can authenticate a request by validating the token alone, without a session lookup. The `@ooneex/jwt` component wraps the [JOSE](https://github.com/panva/jose) library in a single injectable `Jwt` class that signs tokens with HS256, verifies their signature and expiration, and decodes their payload and header.

## Why this component

* **JOSE under the hood.** Signing and verification run on the audited JOSE library, so you get correct, spec-compliant HS256 handling rather than hand-rolled crypto.
* **Stateless auth.** A verified token is proof of identity on its own — no session store, which fits horizontally scaled services and a [middleware](/basics/middleware)-driven request pipeline.
* **Type-safe payloads.** `create<T>` and `getPayload<T>` are generic, so your custom claims (role, permissions, tenant) are typed end to end.
* **Standard claims built in.** `iss`, `sub`, `aud`, `exp`, `iat`, `nbf`, and `jti` are first-class — set them on the payload and the class maps them to the right JOSE setters.
* **Secret from the environment.** The signing secret is read from `JWT_SECRET` via `AppEnv` and injected by the container; the secret never lives in your code.

## How it works

The `Jwt` class is injectable. Its constructor pulls `AppEnv` from the container and throws a `JwtException` immediately if `JWT_SECRET` is missing, so a misconfigured app fails fast at startup rather than at the first request.

| Stage        | What happens                                                                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Construction | `AppEnv` is injected; if `JWT_SECRET` is unset, a `JwtException` (`JWT_SECRET_REQUIRED`) is thrown.                                                          |
| Signing      | `create()` splits standard claims (`iss`, `sub`, `exp`, …) from custom claims, applies them via JOSE setters, and signs with HS256 using the encoded secret. |
| Verifying    | `isValid()` calls `jose.jwtVerify` — it checks both the signature and the expiration, returning `true` or `false` (never throwing).                          |
| Decoding     | `getPayload()` and `getHeader()` decode the token's parts **without** any signature check.                                                                   |

```typescript theme={null}
// The Jwt class is resolved by the container; the secret comes from JWT_SECRET.
import { inject } from "@ooneex/container";
import { Jwt } from "@ooneex/jwt";

constructor(@inject(Jwt) private readonly jwt: Jwt) {}
```

## Methods

| Method       | Signature                                                                                            | Description                                                                                                      |
| ------------ | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `create`     | `create<T>(config?: { payload?: JwtPayloadType<T>; header?: JWTHeaderParameters }): Promise<string>` | Signs and returns a JWT string. Standard claims are read from `payload`; everything else becomes a custom claim. |
| `isValid`    | `isValid(token: string): Promise<boolean>`                                                           | Verifies signature **and** expiration. Returns `true` if valid, `false` on any failure — it does not throw.      |
| `getPayload` | `getPayload<T>(token: string): JwtPayloadType<T>`                                                    | Decodes and returns the payload **without verifying** the signature.                                             |
| `getHeader`  | `getHeader<T>(token: string): T`                                                                     | Decodes and returns the protected header (e.g. `alg`, `typ`, `kid`) without verifying.                           |
| `getSecret`  | `getSecret(): string`                                                                                | Returns the raw `JWT_SECRET` used for signing and verifying.                                                     |

<Warning>
  `getPayload` and `getHeader` only **decode** — they perform no cryptographic check. Anyone can craft a token whose payload says `role: "admin"`. Always call `isValid` (or `create` it yourself) before trusting a decoded payload.
</Warning>

## Algorithms and payload options

The component signs every token with **HS256** (HMAC-SHA256, a symmetric algorithm using a shared secret). The header algorithm is fixed; the table below covers the claims and options you control through the payload.

| Option            | Type                                 | Claim      | Description                                                                              |
| ----------------- | ------------------------------------ | ---------- | ---------------------------------------------------------------------------------------- |
| `iss`             | `string`                             | Issuer     | Who issued the token (e.g. your app or service name).                                    |
| `sub`             | `string`                             | Subject    | Who the token is about — typically the user id.                                          |
| `aud`             | `string \| string[]`                 | Audience   | Intended recipient(s) of the token.                                                      |
| `exp`             | `number \| JwtExpiresInType \| Date` | Expiration | When the token stops being valid. Accepts a duration string like `'15m'`.                |
| `iat`             | `number \| string \| Date`           | Issued At  | When the token was created.                                                              |
| `nbf`             | `number \| string \| Date`           | Not Before | The token is invalid until this time.                                                    |
| `jti`             | `string`                             | JWT ID     | A unique id for the token (useful for revocation lists).                                 |
| *(any other key)* | `unknown`                            | custom     | Anything not in the list above is signed as a custom claim and returned by `getPayload`. |

`JwtExpiresInType` is a relative-duration string accepted by `exp`:

| Suffix | Unit    | Example |
| ------ | ------- | ------- |
| `s`    | seconds | `'30s'` |
| `m`    | minutes | `'15m'` |
| `h`    | hours   | `'2h'`  |
| `d`    | days    | `'7d'`  |
| `w`    | weeks   | `'1w'`  |
| `y`    | years   | `'1y'`  |

## Signing a token

Issue a token on login. Put the user id in `sub`, set a short expiration, and add any custom claims you want to read back later.

```typescript theme={null}
interface AccessClaims {
  role: string;
  permissions: string[];
}

const token = await this.jwt.create<AccessClaims>({
  payload: {
    sub: "usr_abc123",
    iss: "my-app",
    aud: "my-api",
    exp: "15m", // short-lived access token
    role: "admin",
    permissions: ["read", "write"],
  },
});
```

You can also set protected header parameters such as a key id:

```typescript theme={null}
const token = await this.jwt.create({
  payload: { sub: "usr_abc123", exp: "1h" },
  header: { kid: "key-2026-06" },
});
```

## Verifying a token

Verify on every request. `isValid` returns a boolean and never throws, so it is safe to branch on directly.

```typescript theme={null}
const ok = await this.jwt.isValid(token);

if (!ok) {
  // signature failed or the token has expired
  throw new Error("Invalid or expired token");
}
```

## Decoding without verifying

`getPayload` and `getHeader` read the token's contents without checking the signature — handy for inspecting an expired token, reading a `kid` before verification, or debugging. Treat the result as untrusted until `isValid` passes.

```typescript theme={null}
const header = this.jwt.getHeader(token);
console.log(header.alg); // "HS256"

const payload = this.jwt.getPayload<{ role: string }>(token);
console.log(payload.sub);  // "usr_abc123"
console.log(payload.role); // "admin" — NOT yet verified
```

## In an auth flow

The two halves of the flow are: issue a token when the user authenticates, then verify it in a middleware on every subsequent request. The middleware verifies first, then decodes, then attaches the user to the context for the controller. See [Authentication](/security/auth) for the full login flow and [Users](/security/users) for the resolved user model.

```typescript theme={null}
import { inject } from "@ooneex/container";
import type { ContextType } from "@ooneex/controller";
import { Jwt } from "@ooneex/jwt";
import { decorator, type IMiddleware } from "@ooneex/middleware";

@decorator.middleware()
export class AuthMiddleware implements IMiddleware {
  constructor(@inject(Jwt) private readonly jwt: Jwt) {}

  public async handler(context: ContextType): Promise<ContextType> {
    const authHeader = context.header.get("Authorization");

    if (!authHeader?.startsWith("Bearer ")) {
      context.response.exception("Missing authorization header", { status: 401 });
      return context;
    }

    const token = authHeader.substring(7);

    // 1. Verify signature + expiration before trusting anything.
    if (!(await this.jwt.isValid(token))) {
      context.response.exception("Invalid or expired token", { status: 401 });
      return context;
    }

    // 2. Now it is safe to decode and use the claims.
    const payload = this.jwt.getPayload<{ role: string }>(token);
    context.user = { id: payload.sub, role: payload.role };

    return context;
  }
}
```

Because the middleware short-circuits with `context.response.exception(...)` on failure, an unauthenticated request never reaches the controller. See [Middleware](/basics/middleware) for how the pipeline runs.

## Error handling

The constructor throws a `JwtException` when `JWT_SECRET` is missing. `JwtException` extends the framework `Exception`, so it carries a `message`, a `key`, and a `status`. Catch it to distinguish JWT configuration errors from other failures.

```typescript theme={null}
import { Jwt, JwtException } from "@ooneex/jwt";

try {
  // Resolving Jwt without JWT_SECRET set throws here.
  const token = await this.jwt.create({ payload: { sub: "usr_abc123" } });
} catch (error) {
  if (error instanceof JwtException) {
    console.error(error.message); // "JWT secret is required. ..."
    console.error(error.key);     // "JWT_SECRET_REQUIRED"
    console.error(error.status);  // 500
  }
}
```

Note that `isValid` does **not** throw on a bad or expired token — it returns `false`. Reserve `try/catch` for configuration and decoding errors (e.g. `getPayload` on a malformed string).

## Best practices

* **Keep the secret in the environment.** Set `JWT_SECRET` via `AppEnv`/`.env`, never in source. Use a long, random value and rotate it when needed.
* **Short-lived access tokens.** Prefer `exp: '15m'` for access tokens and a separate longer-lived refresh token; a leaked access token then expires quickly.
* **Never trust an unverified decode.** Call `isValid` before reading claims with `getPayload`. A decoded payload is attacker-controlled until the signature checks out.
* **Pin the algorithm.** The component signs only with HS256 — do not accept tokens whose header advertises a different `alg`. Reject anything you did not issue.
* **Set meaningful claims.** Use `sub` for the user id, `iss`/`aud` to scope a token to your app, and `jti` if you need per-token revocation.
* **Verify in one place.** Centralize verification in an auth middleware so every protected route enforces it consistently, rather than checking tokens in individual controllers.

## Related

* [Authentication](/security/auth) — the login flow that issues tokens.
* [Users](/security/users) — the user model attached to the request after verification.
* [Middleware](/basics/middleware) — the pipeline where tokens are verified per request.
* [Utilities: JWT](/utilities/jwt) — helper utilities for working with tokens.
