|
| 1 | +# AI Agent Guidelines for express-openid-connect |
| 2 | + |
| 3 | +This document provides context and guidelines for AI coding assistants working with the express-openid-connect codebase. |
| 4 | + |
| 5 | +## Your Role |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Working Principles |
| 12 | + |
| 13 | +Apply these on every task in this repo — they keep changes correct, small, and reviewable. |
| 14 | + |
| 15 | +- **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. |
| 16 | +- **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. |
| 17 | +- **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. |
| 18 | +- **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. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Project Overview |
| 23 | + |
| 24 | +**express-openid-connect** is Express middleware to protect web applications using OpenID Connect. |
| 25 | + |
| 26 | +- **Language:** JavaScript (CommonJS), with hand-written TypeScript types in `index.d.ts` |
| 27 | +- **Tech Stack:** Express (peer dep `>= 4.17.0`) · `openid-client` v6 (OIDC/OAuth) · `jose` (JWT/keys) · `joi` (config validation) · `http-errors` |
| 28 | +- **Package Manager:** npm |
| 29 | +- **Minimum Platform Version:** Node.js `^20.19.0 || ^22.12.0 || >= 23.0.0` (see `engines` in `package.json`) |
| 30 | +- **Dependencies:** `openid-client` 6, `jose` 6, `joi` 17, `cookie` 0.7 (+7 more) · test: Mocha, Chai, Sinon, nock, tsd, Puppeteer — see `package.json` for the full list |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## Project Structure |
| 35 | + |
| 36 | +``` |
| 37 | +. |
| 38 | +├── index.js # Public entry — re-exports auth, requiresAuth, attemptSilentLogin, SessionExpiredError |
| 39 | +├── index.d.ts # Hand-written TypeScript type definitions (the typed public contract) |
| 40 | +├── .version # Version source of truth (mirrors package.json "version") |
| 41 | +├── middleware/ # The exported middleware factories |
| 42 | +│ ├── auth.js # `auth()` — mounts login/logout/callback routes + session |
| 43 | +│ ├── requiresAuth.js # `requiresAuth()` / claim guards |
| 44 | +│ ├── attemptSilentLogin.js |
| 45 | +│ └── unauthorizedHandler.js |
| 46 | +├── lib/ # Internal implementation |
| 47 | +│ ├── config.js # Joi schema — the config/public-option contract |
| 48 | +│ ├── client.js # openid-client setup + telemetry / User-Agent headers |
| 49 | +│ ├── context.js # Request context, OIDC flow logic (largest module) |
| 50 | +│ ├── appSession.js # Encrypted cookie session handling |
| 51 | +│ ├── crypto.js # Cookie encryption/signing |
| 52 | +│ ├── transientHandler.js |
| 53 | +│ ├── errors.js # SessionExpiredError (typed error) |
| 54 | +│ └── hooks/, utils/ # getLoginState hook, helpers |
| 55 | +├── test/ # Unit tests (Mocha + Chai + Sinon + nock) |
| 56 | +├── end-to-end/ # Slow browser integration tests (Puppeteer + local oidc-provider) |
| 57 | +├── examples/ # Runnable example apps (referenced by EXAMPLES.md and e2e tests) |
| 58 | +└── docs/ # Generated TypeDoc API output (do not hand-edit) |
| 59 | +``` |
| 60 | + |
| 61 | +### Key Files |
| 62 | + |
| 63 | +| File | Why it matters | |
| 64 | +|------|----------------| |
| 65 | +| `index.js` | Public export surface — anything not re-exported here is internal | |
| 66 | +| `index.d.ts` | Hand-written types; must stay in lockstep with the runtime config/options | |
| 67 | +| `lib/config.js` | Joi schema defining every valid config option and its default — the option contract | |
| 68 | +| `lib/context.js` | Core OIDC flow (login/callback/session) — most behavior changes land here | |
| 69 | +| `lib/client.js` | Where the `Auth0-Client` telemetry header and `User-Agent` are set | |
| 70 | +| `.version` | Version source of truth, kept in sync with `package.json` | |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +## Boundaries |
| 75 | + |
| 76 | +### ✅ Always Do |
| 77 | + |
| 78 | +- Run `npm run test` (unit) and `npm run lint` before committing. |
| 79 | +- Follow existing CommonJS style and naming conventions (Prettier: single quotes, 80-col width). |
| 80 | +- Add unit tests under `test/` for new functionality; use `nock` to stub HTTP, never real network. |
| 81 | +- Keep `index.d.ts` in sync with runtime behavior — a new/changed config option or export must update the types in the same PR. |
| 82 | +- Keep `.version` and `package.json` `"version"` in sync (both are version sources). |
| 83 | +- Update `README.md` and `EXAMPLES.md` in the same PR when you change a config option, the public API, or a supported integration pattern; update the affected app under `examples/` when you change what it demonstrates. |
| 84 | +- When adding a **new outbound request path to the OIDC provider**, route it through the existing `createCustomFetch` in `lib/client.js` (and the equivalent in `lib/context.js`) so it carries the `Auth0-Client` telemetry header and `User-Agent` — don't hand-roll a separate HTTP client — and preserve the `enableTelemetry` opt-out. |
| 85 | + |
| 86 | +### ⚠️ Ask First |
| 87 | + |
| 88 | +- **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. |
| 89 | +- Adding new dependencies or bumping existing ones. |
| 90 | +- Modifying public API signatures or the `lib/config.js` Joi schema (options and defaults are the public contract). |
| 91 | +- Changes to CI/CD configuration (`.github/workflows/`). |
| 92 | +- Changing session storage or cookie encryption/format (`lib/appSession.js`, `lib/crypto.js`) — it affects existing sessions. |
| 93 | +- Modifying security-related code (token handling, PKCE/state/nonce, cookie flags). |
| 94 | + |
| 95 | +### 🚫 Never Do |
| 96 | + |
| 97 | +- Commit secrets, API keys, client secrets, or tokens. |
| 98 | +- Log tokens, `id_token`/`access_token` contents, session cookies, or client secrets. |
| 99 | +- Modify generated files by hand: `docs/` (TypeDoc output), `CHANGELOG.md`, `coverage/`, `.nyc_output/`, lock files. |
| 100 | +- Remove or skip failing tests without fixing the underlying cause. |
| 101 | +- Modify `node_modules/`. |
| 102 | +- Weaken secure defaults (cookie `httpOnly`/`secure`/`sameSite`, `RS256`, PKCE/state/nonce) without explicit approval. |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## Security Considerations |
| 107 | + |
| 108 | +This is an OIDC Relying Party middleware; the following are auto-detected from the code and are non-negotiable defaults: |
| 109 | + |
| 110 | +- **Flows & PKCE:** default `response_type` is `id_token` (Implicit with Form Post); the Authorization Code flow (`response_type` includes `code`) uses PKCE and state/nonce via `openid-client`. Do not disable state/nonce/PKCE checks. |
| 111 | +- **ID token signing:** default `idTokenSigningAlg` is `RS256`; `none` is explicitly rejected in `lib/config.js`. HS* algorithms require a `clientSecret`. |
| 112 | +- **Session cookies:** the app session is an encrypted cookie (`lib/appSession.js` + `lib/crypto.js`) with `httpOnly: true` by default, `secure` inferred from an HTTPS `baseURL`, and a configurable `sameSite`. A warning is emitted for insecure cookies over HTTPS. |
| 113 | +- **Secrets:** the `secret` config drives cookie encryption; never log it or commit it. Examples read secrets from env (`examples/.env.sample`), never hard-code them. |
| 114 | +- **Telemetry:** the `Auth0-Client` header is sent by default and user-disablable via `enableTelemetry: false` — never remove the opt-out or send unconditionally. |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md` behind a linked pointer. Read a reference file only when your task needs it. |
| 119 | +
|
| 120 | +## Commands |
| 121 | + |
| 122 | +```bash |
| 123 | +npm run test # unit tests (Mocha, safe — no credentials, HTTP is nocked) |
| 124 | +npm run lint # ESLint |
| 125 | +``` |
| 126 | + |
| 127 | +See [references/commands.md](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. |
| 128 | + |
| 129 | +## Testing |
| 130 | + |
| 131 | +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/`. |
| 132 | + |
| 133 | +See [references/testing.md](references/testing.md) for conventions, mocking, coverage, and the end-to-end tier. Read when writing or running tests. |
| 134 | + |
| 135 | +## Code Style |
| 136 | + |
| 137 | +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. |
| 138 | + |
| 139 | +See [references/code-style.md](references/code-style.md) for naming, patterns, and good/bad examples. Read before writing non-trivial new code. |
| 140 | + |
| 141 | +## Git Workflow |
| 142 | + |
| 143 | +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. |
| 144 | + |
| 145 | +See [references/git-workflow.md](references/git-workflow.md) for full branch/commit/PR conventions. Read when preparing a commit or PR. |
| 146 | + |
| 147 | +## Common Pitfalls |
| 148 | + |
| 149 | +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. |
| 150 | + |
| 151 | +See [references/pitfalls.md](references/pitfalls.md) for the full list with fixes. Read when debugging unexpected behavior. |
| 152 | + |
| 153 | +## Docs Update Rules |
| 154 | + |
| 155 | +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. |
| 156 | + |
| 157 | +See [references/docs-update.md](references/docs-update.md) for the tracked-docs inventory and the code-to-docs mapping. Read when changing public API, config, or examples. |
0 commit comments