KeyloomKeyloom
Keyloom Auth is currently in beta. Feedback and contributions are welcome!
Nextjs

Next.js JWT Utilities

Verify and consume Keyloom JWTs on the server and in middleware using @keyloom/nextjs/jwt-server.

JWT Utilities (server)

Utilities for verifying JWT access/refresh tokens issued by Keyloom.

import {
  extractAccessToken,
  extractRefreshToken,
  verifyJwtToken,
  getJwtSession,
  requireJwtAuth,
  createJwtConfig,
  clearServerJwksCache,
} from "@keyloom/nextjs/jwt-server";

Cookies

  • __keyloom_access: short-lived access token
  • __keyloom_refresh: long-lived refresh token

Create JwtConfig from env

const jwt = createJwtConfig({
  KEYLOOM_JWT_JWKS_URL: process.env.KEYLOOM_JWT_JWKS_URL,
  KEYLOOM_JWT_ISSUER: process.env.KEYLOOM_JWT_ISSUER,
  KEYLOOM_JWT_AUDIENCE: process.env.KEYLOOM_JWT_AUDIENCE,
  KEYLOOM_JWT_CLOCK_SKEW_SEC: process.env.KEYLOOM_JWT_CLOCK_SKEW_SEC,
});

Verify a token

const { valid, claims, error } = await verifyJwtToken(token, {
  jwksUrl: process.env.KEYLOOM_JWT_JWKS_URL!,
  expectedIssuer: process.env.KEYLOOM_JWT_ISSUER,
  expectedAudience: process.env.KEYLOOM_JWT_AUDIENCE,
  clockSkewSec: 60,
});

Get current session (server)

const { user, session, claims } = await getJwtSession({
  jwksUrl: process.env.KEYLOOM_JWT_JWKS_URL!,
  expectedIssuer: process.env.KEYLOOM_JWT_ISSUER,
});
  • user: { id, email? } | null
  • session: { id, userId, expiresAt } | null

Require authentication

const { user, session } = await requireJwtAuth({
  jwksUrl: process.env.KEYLOOM_JWT_JWKS_URL!,
});

Throws Error("Authentication required") when not authenticated.

Middleware access

import type { NextRequest } from "next/server";
import { getJwtSession } from "@keyloom/nextjs/jwt-server";

export function middleware(req: NextRequest) {
  // Beware: network fetch to JWKS may be slower in middleware; rely on server guards if needed
}

JWKS Cache

clearServerJwksCache() clears the in-memory JWKS cache, useful for tests.


Prerequisites

  • JWT strategy enabled or JWKS exposed by your server
  • KEYLOOM_JWT_JWKS_URL set to your /api/auth/jwks endpoint
  • If verifying aud/iss, set KEYLOOM_JWT_AUDIENCE and KEYLOOM_JWT_ISSUER

API reference

FunctionSignatureReturns
createJwtConfig(env)({ KEYLOOM_JWT_JWKS_URL, KEYLOOM_JWT_ISSUER?, KEYLOOM_JWT_AUDIENCE?, KEYLOOM_JWT_CLOCK_SKEW_SEC? })JwtVerifyOptions
verifyJwtToken(token, opts)(string, { jwksUrl, expectedIssuer?, expectedAudience?, clockSkewSec? }){ valid: boolean; claims?; error? }
getJwtSession(opts?)({ jwksUrl, expectedIssuer?, expectedAudience? }){ user, session, claims }
requireJwtAuth(opts?)same as abovethrows on unauthenticated
extractAccessToken()() => string | nullfrom cookie or Authorization
extractRefreshToken()() => string | nullfrom cookie
clearServerJwksCache()() => voidclears cache

Runnable examples

app/api/protected/route.ts
import { requireJwtAuth } from "@keyloom/nextjs/jwt-server";

export async function GET() {
  try {
    const { user, session } = await requireJwtAuth({
      jwksUrl: process.env.KEYLOOM_JWT_JWKS_URL!,
      expectedIssuer: process.env.KEYLOOM_JWT_ISSUER,
    });
    return Response.json({ user, session });
  } catch (e) {
    return Response.json({ error: "unauthorized" }, { status: 401 });
  }
}
app/dashboard/page.tsx
import { getJwtSession } from "@keyloom/nextjs/jwt-server";

export default async function Page() {
  const { user } = await getJwtSession({
    jwksUrl: process.env.KEYLOOM_JWKS_URL!,
  });
  if (!user) return <a href="/sign-in">Sign in</a>;
  return <p>Hi {user.id}</p>;
}

Error handling

  • Map verification errors to 401; avoid leaking details to clients
  • For expired tokens, prompt a refresh flow (server issues new access using refresh)
const { valid, error } = await verifyJwtToken(token, { jwksUrl });
if (!valid) return Response.json({ error: "invalid_token" }, { status: 401 });

Edge notes

  • Avoid verifying refresh tokens in middleware; use server routes
  • Cache JWKS in memory for a few minutes to minimize fetches

Performance

  • Verify once at request boundary and pass claims through
  • Batch external calls after verification; do not mix verification and data loads

Troubleshooting

  • fetch to JWKS blocked in middleware: verify your deployment allows outgoing fetches at edge
  • kid not found: ensure your JWKS contains active and overlapping keys during rotation
  • iss mismatch: set KEYLOOM_JWT_ISSUER to your public app URL

See also

Next steps

  • Add refresh flow endpoints and integrate session UX

How is this guide?