HelloJohn / docs
SDKsNode.js SDK

Verify Token

Verify HelloJohn JWTs in Node.js — local JWKS verification, API-based verification, and extracting claims from access tokens.

Verify Token

Verify HelloJohn access tokens in your Node.js backend to authenticate API requests.


Verify the JWT signature locally using the HelloJohn JWKS endpoint. No network call to HelloJohn per request:

import { HelloJohn } from "@hellojohn/node";

const hj = new HelloJohn({
  tenantId: process.env.HELLOJOHN_TENANT_ID!,
  secretKey: process.env.HELLOJOHN_SECRET_KEY!,
});

// Returns the decoded payload or throws
const payload = await hj.verifyToken(token);
console.log(payload.sub); // User ID

verifyToken Options

const payload = await hj.verifyToken(token, {
  // Optional: validate additional claims
  audience: process.env.HELLOJOHN_TENANT_ID, // Default: your tenant ID
  clockSkewSeconds: 30,                      // Allow 30s clock drift
});

Return Value:

interface TokenPayload {
  sub: string;           // User ID
  sid: string;           // Session ID
  iss: string;           // Issuer: "https://api.hellojohn.dev"
  aud: string;           // Audience: your tenant ID
  iat: number;           // Issued at (Unix timestamp)
  exp: number;           // Expiry (Unix timestamp)
  email?: string;        // User email (if included in claims)
  org_id?: string;       // Active organization ID
  org_role?: string;     // User's role in active org
  [key: string]: unknown; // Custom claims
}

API-Based Verification

For instant revocation (verifies against HelloJohn's session store):

const { valid, payload, error } = await hj.verifyTokenRemote(token);

if (!valid) {
  // Token is expired, revoked, or invalid
  throw new Error(error?.message ?? "Unauthorized");
}

console.log(payload.sub); // User ID

This makes one API call to HelloJohn per request. Use for high-security endpoints where you need to detect revoked sessions immediately.


Express Middleware

A reusable middleware function:

import { HelloJohn } from "@hellojohn/node";
import type { Request, Response, NextFunction } from "express";

const hj = new HelloJohn({
  tenantId: process.env.HELLOJOHN_TENANT_ID!,
  secretKey: process.env.HELLOJOHN_SECRET_KEY!,
});

export async function requireAuth(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const token = req.headers.authorization?.split(" ")[1];

  if (!token) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    const payload = await hj.verifyToken(token);
    req.auth = { userId: payload.sub, sessionId: payload.sid };
    next();
  } catch {
    res.status(401).json({ error: "Invalid token" });
  }
}

// Usage
app.get("/api/protected", requireAuth, (req, res) => {
  res.json({ userId: req.auth.userId });
});

Extracting Claims

HelloJohn tokens include custom claims configured for your tenant:

const payload = await hj.verifyToken(token);

// Standard claims
const userId = payload.sub;
const sessionId = payload.sid;
const expiresAt = new Date(payload.exp * 1000);

// Organization claims
const orgId = payload.org_id;
const orgRole = payload.org_role;

// Custom claims (set in your tenant config)
const userRole = payload.role as string;
const plan = payload.plan as string;

Configure custom claims in your tenant settings or via the Admin API.


Without the SDK

Using jose directly:

import * as jose from "jose";

const JWKS = jose.createRemoteJWKSet(
  new URL(`https://api.hellojohn.dev/v1/jwks/${process.env.HELLOJOHN_TENANT_ID}`)
);

async function verifyToken(token: string) {
  const { payload } = await jose.jwtVerify(token, JWKS, {
    issuer: "https://api.hellojohn.dev",
    audience: process.env.HELLOJOHN_TENANT_ID,
    algorithms: ["EdDSA"],
  });
  return payload;
}

On this page