Express.js Middleware

Verify Twingate Identity Firewall JWTs in an Express.js application.

This guide shows how to verify Twingate Identity Firewall JWTs in an Express.js application using the jose library. For architecture details and the full JWT reference, see the Identity Firewall for Web Apps overview.

Prerequisites

  • Node.js (LTS recommended)
  • An Express.js application

Configure the Gateway to Inject the JWT

The Gateway injects no headers by default. On the Web App Resource, add a request header so the Gateway sends the signed JWT on every request. The recommended convention is a Bearer token in the Authorization header:

Header KeyValue Template
AuthorizationBearer {{jwt}}

The header name and format are configurable at the Gateway/Resource level. This guide reads the JWT from Authorization using the Bearer scheme. See Request Headers for the full list of options and template variables.

Install Dependencies

npm install jose

jose is a standards-compliant JWT library that runs in Node.js, Deno, and edge runtimes.

Add the Middleware

Save this file as twingate-middleware.js in your project.

import { createRemoteJWKSet, jwtVerify } from "jose";
export function twingateAuth(jwksUrl) {
const jwks = createRemoteJWKSet(new URL(jwksUrl));
return async (req, res, next) => {
const auth = req.headers.authorization ?? "";
if (!auth) {
req.twingateIdentity = null;
return next();
}
const parts = auth.split(" ");
if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
return res.status(401).json({ error: "Malformed Authorization header" });
}
try {
const { payload } = await jwtVerify(parts[1], jwks, {
algorithms: ["ES256"],
requiredClaims: ["exp", "iat"],
clockTolerance: "30s",
});
req.twingateIdentity = payload;
return next();
} catch {
return res.status(401).json({ error: "Invalid token" });
}
};
}

How It Works

JWKS Setup

createRemoteJWKSet fetches and caches the Gateway’s public keys automatically. You create it once at middleware initialization, and jose handles key rotation and refresh behind the scenes.

Request Handling

When no Authorization header is present, the middleware sets req.twingateIdentity = null and calls next(). This lets health checks and public routes work without authentication.

Token Verification

The middleware parses the Bearer token, verifies the ES256 signature against the JWKS, requires exp/iat claims, and allows 30 seconds of clock tolerance. On success, the full JWT payload is attached to req.twingateIdentity. On failure, a 401 JSON response is returned.

Use Identity in Your App

Reading Identity

A /me route that reads req.twingateIdentity and returns the user info:

app.get("/me", (req, res) => {
if (!req.twingateIdentity) {
return res.status(401).json({ error: "Unauthorized" });
}
const { user, device, resource } = req.twingateIdentity;
res.json({ user, device, resource });
});

Group-Based Authorization

An /admin route that checks Group membership before granting access:

app.get("/admin", (req, res) => {
if (!req.twingateIdentity) {
return res.status(401).json({ error: "Unauthorized" });
}
const { groups } = req.twingateIdentity.user;
if (!groups.includes("admin")) {
return res.status(403).json({ error: "Forbidden" });
}
res.json({ message: "Welcome, admin" });
});

Configuration

VariableDescription
TWINGATE_JWKS_URLYour Twingate tenant’s JWKS endpoint: https://<your-tenant>.twingate.com/api/v1/jwk/ec

Wire it up with a one-liner:

app.use(twingateAuth(process.env.TWINGATE_JWKS_URL));

Next Steps

Last updated 3 hours ago