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. Issue tokens from your appAuthenticate users with the AscendKit SDK and obtain access tokens for API calls.
- 2. Verify in middlewareAdd Express middleware that verifies the access token and rejects unauthenticated requests.
- 3. Scope to the projectEach 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.
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
- Add authentication to FastAPI — the Python equivalent, which ships a dedicated dependency helper.
- Add auth to a Hono API — the same approach on an edge runtime, where the constraints differ.
- Add authentication to FastAPI — the Python equivalent, which has a dedicated SDK helper.
Start with one API key
Auth, email, surveys, and journeys share one user record, so you ship this without stitching vendors together.
Start free