Plugins

JWT

Issue signed JWT access tokens and publish a JWKS endpoint for verification.

The JWT plugin manages a rotating signing key in the database, issues short-lived JWT access tokens for the active session, and publishes the public keys at a JWKS route so any service can verify those tokens without calling back to Kernia.

Installation

Add the plugin to your server

Pass jwt() to your Kernia config. Set the issuer to your base URL so the iss claim is correct.

auth.py
import os

from kernia import KerniaOptions
from kernia.auth import init
from kernia.plugins.jwt import JwtOptions, jwt

from .db import adapter

auth = init(KerniaOptions(
    database=adapter,
    secret=os.environ["KERNIA_SECRET"],
    base_url=os.environ["KERNIA_BASE_URL"],
    plugins=[jwt(JwtOptions(issuer=os.environ["KERNIA_BASE_URL"]))],
))

Add the client plugin

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { jwtClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  baseURL: "/api/auth",
  plugins: [jwtClient()],
});

Usage

Get a token

The recommended path is authClient.token(), which calls /token with the active session and returns a fresh JWT:

const { data } = await authClient.token();
const jwt = data?.token;

A JWT is also returned in the set-auth-jwt response header whenever the client calls getSession(), so you can capture it without an extra round trip.

Verify a token

Other services fetch the public keys from the JWKS endpoint and verify the token locally — no shared secret, no call back to Kernia:

GET /api/auth/jwks

Rotate keys

POST /api/auth/jwks/rotate marks the current key inactive and mints a new one. The old key stays in the JWKS during the grace period so tokens already in flight keep verifying. Protect the route with rotate_admin_token or an admin session.

Options

Pass these on JwtOptions:

OptionTypeDefaultDescription
issuerstrNoneValue for the iss claim. Set to your base URL.
audiencestrNoneValue for the aud claim.
algorithmstr"EdDSA"Key-pair algorithm: EdDSA, ES256, ES384, ES512, RS256, or PS256.
expiration_timeint | str900Token lifetime in seconds or a string like "15m", "1h", "7d".
define_payload(session) -> dictNoneCustomize the JWT payload issued by /token.
get_subject(session) -> strNoneOverride the sub claim.
jwks_pathstr"/jwks"Path the JWKS is served from.
remote_urlstrNonePublish keys remotely; disables the local /jwks route.
rotation_intervalintNoneSeconds after which a key triggers rotation.
grace_periodintNoneSeconds a rotated key stays in the JWKS for in-flight tokens.
rotate_admin_tokenstrNoneIf set, /jwks/rotate requires this bearer token.

Schema

Adds the jwk table holding each signing key: id, algorithm, public key, encrypted private key, active flag, and timestamps.

Private keys are encrypted at rest with your KERNIA_SECRET. Rotating that secret invalidates stored keys, so plan a rotation window if you ever change it.