@aneeshgusain/next-gen-auth-server
v0.1.13
Published
Express middleware for verifying Next-Gen Auth (OIDC) access tokens — no manual JWT/JWKS handling required
Maintainers
Readme
@aneeshgusain/next-gen-auth-server
Express middleware for verifying Next-Gen Auth (OAuth 2.0 / OpenID Connect) access
tokens. Handles JWKS fetching, key caching, key rotation, JWT verification, and
fetching user profile data internally — no jose, manual JWT/JWKS code, or
raw fetch calls required in your app.
Install
npm install @aneeshgusain/next-gen-auth-serverUsage
import express from "express";
import { createAuthMiddleware } from "@aneeshgusain/next-gen-auth-server";
const app = express();
const requireAuth = createAuthMiddleware({
provider: "https://next-gen-auth.onrender.com",
});
app.get("/protected", requireAuth, (req, res) => {
res.json({ message: `Hello ${req.user.sub}` });
});That's the entire integration. The middleware:
- Fetches the provider's public keys from
/.well-known/jwks.jsonon first use - Caches them, and automatically re-fetches if a token references an unrecognized
kid(handles key rotation transparently) - Verifies the token's signature, issuer, and expiry
- Attaches the decoded claims to
req.user - Attaches a ready-to-use
req.getUserInfo()function, already bound to the request's token - Responds with a
401automatically on any failure — no try/catch needed in your route handlers
Getting the user's email, name, and other profile data
Access tokens only ever carry sub, scope, iss, aud, exp, and iat —
never the person's email or name. For that, call req.getUserInfo(), which
is already wired up by the middleware with no setup:
app.get("/profile", requireAuth, async (req, res) => {
const token = req.headers.authorization.split(" ")[1];
const user = await req.getUserInfo({provider: "https://next-gen-auth.onrender.com"}, token); // { sub, email, given_name, family_name, ... }
res.json(user);
});Only call this on routes that actually need profile data — if all you need
is the user's ID, req.user.sub alone is enough and avoids an extra network
call.
Optional authentication
For routes that behave differently for logged-in vs. anonymous users, without requiring auth:
import { createOptionalAuthMiddleware } from "@aneeshgusain/next-gen-auth-server";
const optionalAuth = createOptionalAuthMiddleware({
provider: "https://next-gen-auth.onrender.com",
});
app.get("/feed", optionalAuth, (req, res) => {
if (req.user) {
res.json({ message: `Personalized feed for ${req.user.sub}` });
} else {
res.json({ message: "Generic public feed" });
}
});If no token is present, req.user is left undefined and the request proceeds normally.
If a token is present but invalid, it's silently ignored (treated as anonymous)
rather than rejected. req.getUserInfo() is also available here when req.user is set.
Using this outside Express
If there's no req/res to attach to (a WebSocket handler, a cron job, a
script, another framework), use the standalone functions instead.
Verifying a token:
import { createTokenVerifier } from "@aneeshgusain/next-gen-auth-server";
const verifyAccessToken = createTokenVerifier({ provider: "https://next-gen-auth.onrender.com" });
const claims = await verifyAccessToken(someToken); // throws AuthVerificationError on failureFetching user info, configured once and reused:
import { createUserInfoFetcher } from "@aneeshgusain/next-gen-auth-server";
const token = req.headers.authorization.split(" ")[1];
const getUserInfo = createUserInfoFetcher({ provider: "https://next-gen-auth.onrender.com" });
const user = await getUserInfo(token); // just the token, every callFetching user info, one-off (provider passed every call):
import { getUserInfo } from "@aneeshgusain/next-gen-auth-server";
const user = await getUserInfo({ provider: "https://next-gen-auth.onrender.com" }, token);If you're inside an Express route already wrapped in requireAuth or
createOptionalAuthMiddleware, you never need any of these three — use
req.user and req.getUserInfo() instead.
API
createAuthMiddleware(config)
| Option | Required | Description |
| ------------ | -------- | --------------------------------------------------------------------------------- |
| provider | yes | Base URL of your Next-Gen Auth provider |
| audience | no | If set, rejects tokens whoseaud claim doesn't match (pass your client_id) |
Returns an Express middleware. On success, sets req.user to the decoded token
claims and req.getUserInfo() to a ready-to-call function bound to the
request's token. On failure, responds 401 directly — the request never
reaches your handler.
createOptionalAuthMiddleware(config)
Same config shape. Never rejects a request — req.user and req.getUserInfo
are set if a valid token was present, otherwise left undefined.
requireScope(scope)
Returns a middleware that checks req.user.scope for the given value. Use it
after createAuthMiddleware in your route's middleware chain. Responds 403
if the scope is missing.
createTokenVerifier(config)
Returns a function (token) => Promise<AuthClaims> for verifying a token
outside Express. Throws AuthVerificationError on failure.
getUserInfo(config, token)
Fetches user claims from the provider's /userinfo endpoint. Standalone —
requires provider on every call. Prefer req.getUserInfo() inside Express,
or createUserInfoFetcher if calling this repeatedly outside Express.
createUserInfoFetcher(config)
Returns a function (token) => Promise<Record<string, unknown>>, with
provider configured once instead of on every call.
Error handling
All verification failures throw (or, in the middleware, respond with) one of
these messages: "access token expired", "invalid token signature",
"invalid or malformed access token", or "missing access token". The
WWW-Authenticate response header is set automatically per the Bearer token
spec, so standard HTTP clients and tooling can introspect the failure reason.
Security notes
- Token verification uses the provider's public key only — this package never needs or accepts a private key or client secret.
- The JWKS is fetched over HTTPS and cached in memory per process. If you run multiple server instances, each will fetch and cache independently — this is expected and fine, since JWKS responses are public, cacheable data.
License
MIT
