Better Auth for Express 5: mount the auth routes and protect APIs with requireAuth
Code3 filesContext~405 tokensscan passed
Install
$
genpm add @yohangel/auth-expressWhat you get
- Source in src/lib/auth-express/, 3 files. (4.3 kB)
- AI rules in src/lib/auth-express/AGENTS.md, plus IDE rule files.
- Resolves @yohangel/auth for you.
README
This package has no README.
~405 tokens→ src/lib/auth-express/AGENTS.md→ .cursor/rules/genpm-yohangel-auth-express.mdc
This is exactly what your AI reads when it works in src/lib/auth-express. Nothing else is added to its context.
@yohangel/auth-express — rules for AI agents
Purpose
Connects @yohangel/auth to Express 5: mounts Better Auth on /api/auth/* and provides requireAuth / optionalAuth middleware that put the session in res.locals.
Module map
index.ts—mountAuth(app, opts),requireAuth,optionalAuth,AuthLocalstype,createAuthre-export.
Integration (do this after installing)
- Order matters — mount auth BEFORE body parsers:
import express from 'express'; import { mountAuth, requireAuth } from './lib/auth-express/index.js'; const app = express(); mountAuth(app, { sendEmail }); // /api/auth/* app.use(express.json()); app.get('/api/me', requireAuth, (req, res) => res.json({ id: res.locals.user.id })); - Frontend on another origin:
app.use(cors({ origin: ['https://app.example.com'], credentials: true }))beforemountAuth, and list that origin inAUTH_TRUSTED_ORIGINS. - Behind a proxy/CDN set
app.set('trust proxy', 1)andAUTH_IP_HEADERso rate limits use the real client IP. - Express 4 is not supported (route syntax
*splatis Express 5). Project must be ESM ("type": "module").
Conventions
- Protect routes with
requireAuth; readres.locals.user/res.locals.session(typed byAuthLocals). - Authorization (who can do what) goes in your handlers after
requireAuth, never on the client.
Don't
- Don't put
express.json()beforemountAuth(Better Auth must read the raw body). - Don't use
cors({ origin: '*', credentials: true }). - Don't return
res.locals.session(contains the token) to the client.
# @yohangel/auth-express — rules for AI agents
## Purpose
Connects `@yohangel/auth` to Express 5: mounts Better Auth on `/api/auth/*` and provides `requireAuth` / `optionalAuth` middleware that put the session in `res.locals`.
## Module map
- `index.ts` — `mountAuth(app, opts)`, `requireAuth`, `optionalAuth`, `AuthLocals` type, `createAuth` re-export.
## Integration (do this after installing)
1. Order matters — mount auth BEFORE body parsers:
```ts
import express from 'express';
import { mountAuth, requireAuth } from './lib/auth-express/index.js';
const app = express();
mountAuth(app, { sendEmail }); // /api/auth/*
app.use(express.json());
app.get('/api/me', requireAuth, (req, res) => res.json({ id: res.locals.user.id }));
```
2. Frontend on another origin: `app.use(cors({ origin: ['https://app.example.com'], credentials: true }))` before `mountAuth`, and list that origin in `AUTH_TRUSTED_ORIGINS`.
3. Behind a proxy/CDN set `app.set('trust proxy', 1)` and `AUTH_IP_HEADER` so rate limits use the real client IP.
4. Express 4 is not supported (route syntax `*splat` is Express 5). Project must be ESM (`"type": "module"`).
## Conventions
- Protect routes with `requireAuth`; read `res.locals.user` / `res.locals.session` (typed by `AuthLocals`).
- Authorization (who can do what) goes in your handlers after `requireAuth`, never on the client.
## Don't
- Don't put `express.json()` before `mountAuth` (Better Auth must read the raw body).
- Don't use `cors({ origin: '*', credentials: true })`.
- Don't return `res.locals.session` (contains the token) to the client.
The exact tree that will be injected, after .genpmignore. Pinned to
// @yohangel/auth-express — Better Auth en Express 5. Monta las rutas ANTES de express.json() y protege rutas con
// `requireAuth`. La sesión queda en `res.locals.session` / `res.locals.user`.
import { fromNodeHeaders, toNodeHandler } from 'better-auth/node';
import type { Express, NextFunction, Request, RequestHandler, Response } from 'express';
import { type AuthSession, type CreateAuthOptions, createAuth, getAuth } from '../auth/index.js';
export { createAuth };
/** Crea la instancia (si no existe) y monta /api/auth/* (Express 5: `*splat`). Llamar antes de `express.json()`. */
export function mountAuth(app: Express, opts: CreateAuthOptions & { basePath?: string } = {}): void {
const { basePath = '/api/auth', ...authOpts } = opts;
const auth = createAuth(authOpts);
app.all(`${basePath}/*splat`, toNodeHandler(auth));
}
export type AuthLocals = { session: AuthSession['session'] | null; user: AuthSession['user'] | null };
async function load(req: Request, res: Response): Promise<AuthSession | null> {
const s = await getAuth().api.getSession({ headers: fromNodeHeaders(req.headers) });
res.locals.session = s?.session ?? null;
res.locals.user = s?.user ?? null;
return s;
}
/** 401 JSON si no hay sesión válida. */
export const requireAuth: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!(await load(req, res))) {
res.status(401).json({ error: 'unauthorized' });
return;
}
next();
} catch (e) {
next(e);
}
};
/** Carga la sesión si existe, sin bloquear. */
export const optionalAuth: RequestHandler = async (req, res, next) => {
try {
await load(req, res);
next();
} catch (e) {
next(e);
}
};
This package declares no MCP servers.
| Version | Commit | Published | Scan |
|---|---|---|---|
| 1.0.0 | 7145b9d | 2 hours ago | ✔ scan passed |
- npm
- none
- proposed
- GenPM proposes the npm command and runs it only if you say yes.
- scan
- scan passed · 0 findings
- commit
- auth-express@1.0.0 → 7145b9d58055b5145085782c958b7577f1d65276 · verified after fetch
- scripts
- None. GenPM never runs package code.
- license
- MIT
- report
- See something wrong?