Use case

Add authentication to an Express API

Use AscendKit access tokens to protect an Express API. Verify the token in middleware and resolve each request to a usr_ record scoped by your project's public key. Auth lives alongside email, journeys, and surveys, so you can extend beyond login without new vendors.

How it works

  1. 1. Issue tokens from your app
    Authenticate users with the AscendKit SDK and obtain access tokens for API calls.
  2. 2. Verify in middleware
    Add Express middleware that verifies the access token and rejects unauthenticated requests.
  3. 3. Scope to the project
    Each verified request resolves to a usr_ record scoped by the X-AscendKit-Public-Key header.

How do you verify AscendKit tokens in Express?

With standard JWT verification against a JWKS endpoint. AscendKit issues RS256 access tokens, so any library that can fetch a JWKS and validate a signature works -- `jose` is the usual choice in Node.

There is no AscendKit-specific Express middleware to install, and that is deliberate. The token is a standard JWT, so you are not coupled to us on the backend: the same middleware would verify tokens from any RS256 issuer.

Cache the key set. Fetching JWKS on every request adds a network round-trip to every authenticated call; `jose` handles this for you with createRemoteJWKSet.

middleware/auth.ts
import { createRemoteJWKSet, jwtVerify } from "jose";
import type { Request, Response, NextFunction } from "express";

// Cached across requests -- do not create this per request.
const JWKS = createRemoteJWKSet(
  new URL(`${process.env.ASCENDKIT_API_URL}/api/auth/jwks`),
);

export async function requireUser(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization?.replace(/^Bearer /, "");
  if (!token) return res.status(401).json({ error: "Missing token" });

  try {
    const { payload } = await jwtVerify(token, JWKS);
    res.locals.userId = payload.sub; // usr_...
    next();
  } catch {
    res.status(401).json({ error: "Invalid token" });
  }
}

Where should the token come from?

Your frontend, via the Authorization header. The browser holds an AscendKit session; your frontend exchanges it for a short-lived access token and sends that to your API.

Do not put the token in a query string. It ends up in server logs, proxy logs, and browser history, and access tokens are bearer credentials -- anyone holding one is that user until it expires.

Short expiry is the mitigation that matters. These tokens are deliberately short-lived, so a leaked one has a small window rather than being a permanent credential.

Troubleshooting

Every request returns 401 in production but works locally

Cause: The token was issued against one environment and is being verified against another. Development and production have different keys.

Fix: Confirm the public key on the backend belongs to the same environment the frontend authenticates against.

Intermittent 503s under load

Cause: The JWKS cache expired and several requests tried to refresh it at once.

Fix: Treat 503 as retryable, not as an auth failure. Never sign a user out on a 503 -- that is our outage, not their session expiring.

FAQ

How does AscendKit auth work for a plain Express API?

Your frontend authenticates with AscendKit and sends an access token; Express middleware verifies it and resolves the request to a project user.

Is there signing key support for tokens?

Yes. AscendKit exposes signing keys via JWKS so access tokens can be verified against published keys.

Related guides

Start with one API key

Auth, email, surveys, and journeys share one user record, so you ship this without stitching vendors together.

Start free