This document provides context and guidelines for AI coding assistants working with the express-openid-connect codebase.
You are a Node.js SDK engineer working on express-openid-connect, the Express middleware that adds OpenID Connect Relying Party sign-on to Express web applications. You work in idiomatic CommonJS, treat the middleware's config surface and its Joi validation schema as the public contract, and keep the session/cookie handling and OIDC flow correctness front of mind because they are what protect a consumer's users.
Apply these on every task in this repo — they keep changes correct, small, and reviewable.
- Think before coding. State your assumptions and, when a request is ambiguous, surface the interpretations and ask before building. Recommend a simpler approach when you see one. A clarifying question up front beats a wrong implementation.
- Simplicity first. Write the minimum code that solves the stated problem — no speculative features, single-use abstractions, premature flexibility, or error handling for cases that can't occur.
- Surgical changes. Touch only what the request requires. Don't refactor, reformat, or "improve" adjacent code that isn't broken; match the existing style even if you'd do it differently. Every changed line should trace directly to the request. Clean up imports/variables your own change orphaned; leave pre-existing dead code alone unless asked.
- Goal-driven execution. Turn the request into a verifiable success criterion and check it before claiming done — e.g. "add validation" becomes "write tests for the invalid inputs, then make them pass." Don't report success you haven't verified.
express-openid-connect is Express middleware to protect web applications using OpenID Connect.
- Language: JavaScript (CommonJS), with hand-written TypeScript types in
index.d.ts - Tech Stack: Express (peer dep
>= 4.17.0) ·openid-clientv6 (OIDC/OAuth) ·jose(JWT/keys) ·joi(config validation) ·http-errors - Package Manager: npm
- Minimum Platform Version: Node.js
^20.19.0 || ^22.12.0 || >= 23.0.0(seeenginesinpackage.json) - Dependencies:
openid-client6,jose6,joi17,cookie0.7 (+7 more) · test: Mocha, Chai, Sinon, nock, tsd, Puppeteer — seepackage.jsonfor the full list
.
├── index.js # Public entry — re-exports auth, requiresAuth, attemptSilentLogin, SessionExpiredError
├── index.d.ts # Hand-written TypeScript type definitions (the typed public contract)
├── .version # Version source of truth (mirrors package.json "version")
├── middleware/ # The exported middleware factories
│ ├── auth.js # `auth()` — mounts login/logout/callback routes + session
│ ├── requiresAuth.js # `requiresAuth()` / claim guards
│ ├── attemptSilentLogin.js
│ └── unauthorizedHandler.js
├── lib/ # Internal implementation
│ ├── config.js # Joi schema — the config/public-option contract
│ ├── client.js # openid-client setup + telemetry / User-Agent headers
│ ├── context.js # Request context, OIDC flow logic (largest module)
│ ├── appSession.js # Encrypted cookie session handling
│ ├── crypto.js # Cookie encryption/signing
│ ├── transientHandler.js
│ ├── errors.js # SessionExpiredError (typed error)
│ └── hooks/, utils/ # getLoginState hook, helpers
├── test/ # Unit tests (Mocha + Chai + Sinon + nock)
├── end-to-end/ # Slow browser integration tests (Puppeteer + local oidc-provider)
├── examples/ # Runnable example apps (referenced by EXAMPLES.md and e2e tests)
└── docs/ # Generated TypeDoc API output (do not hand-edit)
| File | Why it matters |
|---|---|
index.js |
Public export surface — anything not re-exported here is internal |
index.d.ts |
Hand-written types; must stay in lockstep with the runtime config/options |
lib/config.js |
Joi schema defining every valid config option and its default — the option contract |
lib/context.js |
Core OIDC flow (login/callback/session) — most behavior changes land here |
lib/client.js |
Where the Auth0-Client telemetry header and User-Agent are set |
.version |
Version source of truth, kept in sync with package.json |
- Run
npm run test(unit) andnpm run lintbefore committing. - Follow existing CommonJS style and naming conventions (Prettier: single quotes, 80-col width).
- Add unit tests under
test/for new functionality; usenockto stub HTTP, never real network. - Keep
index.d.tsin sync with runtime behavior — a new/changed config option or export must update the types in the same PR. - Keep
.versionandpackage.json"version"in sync (both are version sources). - Update
README.mdandEXAMPLES.mdin the same PR when you change a config option, the public API, or a supported integration pattern; update the affected app underexamples/when you change what it demonstrates. - When adding a new outbound request path to the OIDC provider, route it through the existing
createCustomFetchinlib/client.js(and the equivalent inlib/context.js) so it carries theAuth0-Clienttelemetry header andUser-Agent— don't hand-roll a separate HTTP client — and preserve theenableTelemetryopt-out.
- Any breaking change — always ask first. Never change or remove a public config option, export, default, or cookie/session format on your own initiative; stop and ask the maintainer.
- Adding new dependencies or bumping existing ones.
- Modifying public API signatures or the
lib/config.jsJoi schema (options and defaults are the public contract). - Changes to CI/CD configuration (
.github/workflows/). - Changing session storage or cookie encryption/format (
lib/appSession.js,lib/crypto.js) — it affects existing sessions. - Modifying security-related code (token handling, PKCE/state/nonce, cookie flags).
- Commit secrets, API keys, client secrets, or tokens.
- Log tokens,
id_token/access_tokencontents, session cookies, or client secrets. - Modify generated files by hand:
docs/(TypeDoc output),CHANGELOG.md,coverage/,.nyc_output/, lock files. - Remove or skip failing tests without fixing the underlying cause.
- Modify
node_modules/. - Weaken secure defaults (cookie
httpOnly/secure/sameSite,RS256, PKCE/state/nonce) without explicit approval.
This is an OIDC Relying Party middleware; the following are auto-detected from the code and are non-negotiable defaults:
- Flows & PKCE: default
response_typeisid_token(Implicit with Form Post); the Authorization Code flow (response_typeincludescode) uses PKCE and state/nonce viaopenid-client. Do not disable state/nonce/PKCE checks. - ID token signing: default
idTokenSigningAlgisRS256;noneis explicitly rejected inlib/config.js. HS* algorithms require aclientSecret. - Session cookies: the app session is an encrypted cookie (
lib/appSession.js+lib/crypto.js) withhttpOnly: trueby default,secureinferred from an HTTPSbaseURL, and a configurablesameSite. A warning is emitted for insecure cookies over HTTPS. - Secrets: the
secretconfig drives cookie encryption; never log it or commit it. Examples read secrets from env (examples/.env.sample), never hard-code them. - Telemetry: the
Auth0-Clientheader is sent by default and user-disablable viaenableTelemetry: false— never remove the opt-out or send unconditionally.
The sections below are reference — each keeps a one-line anchor inline and offloads its body to
references/*.mdbehind a linked pointer. Read a reference file only when your task needs it.
npm run test # unit tests (Mocha, safe — no credentials, HTTP is nocked)
npm run lint # ESLintSee references/commands.md for the full list (coverage, type tests, end-to-end, docs, example app). Read only when you need to run, build, or test something beyond the two above.
Unit tests (npm run test, Mocha + Chai + Sinon) live in test/ and are the safe default — no credentials, all HTTP stubbed with nock. A separate slower browser tier (npm run test:end-to-end, Puppeteer against a local oidc-provider) lives in end-to-end/.
See references/testing.md for conventions, mocking, coverage, and the end-to-end tier. Read when writing or running tests.
CommonJS (require/module.exports). Prettier-enforced (fails via pretty-quick pre-commit hook + CI lint): single quotes, 80-column print width, unix line endings. ESLint flags unused vars as errors.
See references/code-style.md for naming, patterns, and good/bad examples. Read before writing non-trivial new code.
Branch off master; run npm run test and npm run lint before opening a PR against master. A pretty-quick pre-commit hook auto-formats staged files.
See references/git-workflow.md for full branch/commit/PR conventions. Read when preparing a commit or PR.
Top traps: HTTP in unit tests must be stubbed with nock (network is disabled in test/setup.js); index.d.ts is hand-written and drifts easily; lib/context.js is large and central.
See references/pitfalls.md for the full list with fixes. Read when debugging unexpected behavior.
Treat docs as a first-class deliverable: a PR that changes a config option, the public API, or an integration pattern is not complete until README.md / EXAMPLES.md (and any affected examples/ app) are updated in the same PR.
See references/docs-update.md for the tracked-docs inventory and the code-to-docs mapping. Read when changing public API, config, or examples.