These instructions apply only to the node-oauth2-server repository, the framework-agnostic OAuth 2.0 server library
published as @node-oauth/oauth2-server.
The sibling express-oauth-server repository is outside this file’s scope. Release CI checks out the Express adapter
as an external integration-test target; it is not a runtime dependency of this package. Changes to the adapter’s code,
documentation, security policy, and release process are outside this scope.
Run package commands from the node-oauth2-server repository root; do not expect a parent workspace-level install or
test command. Before changing the package, check README.md, CONTRIBUTING.md, SECURITY.md, and package.json.
Before exploring the repository, read .aiignore and apply its patterns using gitignore syntax,
relative to this repository root. Agents must follow these exclusions explicitly: exclude matching paths from routine
file listings, searches, reads, and manual edits, including when using tools that do not honor .aiignore themselves.
Access an excluded path only when the user’s task specifically requires it. Build, test, coverage, and documentation
commands may create or update excluded output; use the source files when making changes to generated content.
- Preserve OAuth 2.0 interoperability and security. Review behavior against the relevant standards, especially RFC 6749, RFC 6750, RFC 7636, RFC 8252, and the threat-model guidance in RFC 6819.
- Keep
node-oauth2-serverframework-agnostic. Framework-specific behavior belongs in adapters maintained separately from this repository. - Treat authentication, authorization, token, grant, PKCE, parsing, and error-handling changes as security-sensitive. Avoid weakening validation or disclosure of credentials and tokens in logs, errors, tests, or documentation.
- The public API includes JavaScript entry points and TypeScript declarations. Update
index.d.tswhen a public API or type contract changes. - The package uses CommonJS entry points and Promise/
async-oriented APIs. Preserve existing public behavior unless a change is intentional, documented, and tested.
- Runtime code:
index.jsandlib/ - Type declarations:
index.d.ts - Tests:
test/(Mocha files matchingtest/**/*_test.js) - Test suites:
test/unit/,test/integration/, andtest/compliance/; shared fixtures/helpers:test/helpers/ - Mocha configuration:
.mocharc.yml; shared Chai assertions:test/assertions.js - Hand-authored documentation:
docs/guide/and the VitePress home/configuration files - Generated API documentation:
docs/api/ - Formatting and linting: Biome, configured in
biome.json - CI and release validation:
.github/workflows/
The library implements OAuth workflows without owning HTTP routing, a database, user sessions, or a consent UI.
Applications and framework adapters supply those pieces. The main flow is: adapter creates Request/Response
instances, calls OAuth2Server, and sends the populated response; handlers delegate persistence and policy to the
application’s model.
- Public facade:
index.jsexports theOAuth2Serverconstructor,Request,Response,AbstractGrantType, and error classes.lib/server.jsexposesauthenticate(),authorize(), andtoken(). Each call merges method defaults, constructor options, and per-call options in that order, creates a handler, and returns its Promise.index.d.tsdescribes the public TypeScript contracts. - HTTP boundary:
lib/request.jswraps already-parsed headers, method, query, and body;lib/response.jsholds status, headers, and body. Headers are normalized to lowercase. Handlers require these wrapper instances and mutate the response without sending it. The caller must handle both the resolved result or rejected error and the response state, including redirects and authentication headers. - Resource authentication:
lib/handlers/authenticate-handler.jsextracts a bearer token, rejects conflicting token sources, callsmodel.getAccessToken(), checks expiry, and callsmodel.verifyScope()when scopes are requested. It returns the stored token object and manages scope andWWW-Authenticateheaders. - Authorization endpoint:
lib/handlers/authorize-handler.jsvalidates the client, redirect URI, state, and scope, obtains the user through a suppliedauthenticateHandler(or bearer authentication by default), and saves an authorization code through the model.lib/response-types/code-response-type.jsbuilds the code redirect. Only thecoderesponse type is registered;token-response-type.jsis an unimplemented placeholder. - Token endpoint:
lib/handlers/token-handler.jsvalidates form-encoded POST requests, obtains client credentials, delegates client authentication tomodel.getClient(), checks the client’s allowed grants, and dispatches bygrant_type. The saved token data is returned to the caller;lib/models/token-model.jsvalidates and prepares it forlib/token-types/bearer-token-type.jsto serialize into the OAuth response, with cache-prevention headers. Internal scopes are arrays; the token response converts them to a space-delimited string. Bearer is the issued token type;mac-token-type.jsis an unimplemented placeholder. - Grant implementations:
lib/grant-types/contains authorization-code, client-credentials, password, and refresh-token workflows.AbstractGrantTypeshares token generation, expiration, and scope handling. Grants obtain users and credentials through the model and persist results throughsaveToken(). Custom grant constructors are registered throughextendedGrantTypesand implementhandle(request, client); the exported base class provides reusable helpers. Client-specific token lifetimes override server defaults. - Storage and policy boundary:
options.modelis an application-supplied object whose required methods depend on the selected workflow. Hooks cover client/user lookup, token/code lookup and persistence, revocation, scope policy, redirect validation, and optional token/code generation. Hooks may return plain values or Promises.lib/model.jsdocuments this contract and provides throwing stubs and aModel.from()helper; it is not a persistence layer and is not exported by the package entry point. In particular,getClient()must enforce per-client secret policy; PKCE and grant-level secret-presence options do not replace confidential-client authentication. - Security-sensitive sequencing: authorization-code exchange validates the stored code and client, revokes the code, then checks PKCE and redirect binding before issuing tokens. A failed PKCE attempt therefore consumes the code. Refresh exchange validates token ownership, expiry, and requested scope before revocation; scopes cannot expand, and refresh-token rotation is enabled by default. Preserve these orderings when modifying grant logic.
- Shared protocol support:
lib/pkce/pkce.jsandlib/utils/provide PKCE, cryptographic token generation, scope parsing, and date/string helpers. Default token generation uses Node’s cryptographic random bytes; model hooks may override it. PKCE defaults toS256, allowsplainonly when explicitly enabled, and can be required throughrequirePKCE.lib/errors/defines theOAuthErrorhierarchy with OAuth names and HTTP status codes; handlers translate errors into endpoint-specific bodies, redirects, or headers and reject the operation. Token and authorization handlers normalize caught programming/configuration errors toServerError.
Use the lockfile and install dependencies from the repository root:
npm ciIf you change dependencies, regenerate package-lock.json with npm 11 (the version CI uses), including when resolving merge conflicts. Older npm versions drop the cross-platform optional dependencies, and npm ci then fails in CI even though it works locally.
Validation commands:
npm test # pretest runs Biome check, then Mocha runs all three test suites
npm run lint
npm run format
npm run test:coverage
npm run docs:build # for documentation or public JSDoc changesCode style is enforced by the Biome linter and formatter configured in biome.json. Both npm run lint and
npm run format must pass before every commit, along with npm test. Use npm run lint:fix and npm run format:fix
only when you intend to apply automated changes. The configured formatter uses single quotes, spaces, trailing
commas, and a 120-column line width for JavaScript.
Biome currently checks index.js, lib/**/*.js, and test/**/*.js. A passing Biome check does not validate Markdown,
TypeScript declarations, documentation tooling, or workflow files; review those changes separately.
Write tests with Mocha and Chai, using Chai’s should style assertions. Use Sinon for stubbing, and restore stubs and
fake timers after each test so they cannot affect other tests. Reuse the existing test helpers and shared assertions.
Run focused tests during iteration, but run the full package test, lint, and format commands before submitting a change. Add or update tests for every bug fix and feature. Include regression coverage for security-sensitive behavior and boundary cases.
For a focused run, use npm test -- --grep 'suite or test name'; this retains the Biome pretest check.
Measure test coverage with nyc (Istanbul) via npm run test:coverage. Strive for the highest possible test coverage
across statements, branches, functions, and lines, including error paths and boundary cases. The test workflows
currently enforce a minimum coverage of 98% using coverage/lcov.info. The coverage command does not run the
pretest hook, so it does not replace the required Biome checks.
package.json is the source of truth for supported Node.js versions. Follow its engines.node declaration when
developing, rather than prescribing a separate version policy here. If a change relies on a newer Node.js feature,
verify it against that declaration and document any intentional compatibility change. Development tools may require a
newer Node.js version than the library’s runtime minimum; check their engine requirements if setup fails without
changing the library’s support policy merely to match the local toolchain.
- Add or update JSDoc for new or changed public APIs and keep
index.d.tsin sync. - Do not manually edit files under
docs/api/. Update source JSDoc inlib/, then runnpm run docs:apifrom the repository root. - Edit the guide under
docs/guide/directly.npm run docs:setupcreates the contribution-guide link used by the docs build; edit the rootCONTRIBUTING.mdrather thandocs/guide/contributing.md. - Use the
docs:*scripts inpackage.json; thenpm run docsexample inCONTRIBUTING.mdis outdated. - Keep README, API documentation, migration notes, and type declarations aligned with behavior. Do not update generated output without checking whether the corresponding source change is present.
- Do not edit
CHANGELOG.mdor bump release versions for ordinary feature or bug-fix pull requests; maintainers handle release bookkeeping.
Follow CONTRIBUTING.md for the established contribution process, including opening or discussing
an issue before an ordinary feature or bug-fix pull request. Follow the private reporting process below for security
issues. The branching policy below takes precedence over the guide’s outdated development instructions.
Use trunk-based development with master as the trunk. The development branch is deprecated. Create feature or fix
branches from master, keep them closely aligned with it, and target pull requests to master. The master name is
retained for historical continuity.
Follow the commit message convention in CONTRIBUTING.md.
Keep commits focused, avoid unrelated dependency updates, describe test/reproduction steps in pull requests, and do not include secrets or real credentials in fixtures, examples, logs, or documentation.
Never report a suspected vulnerability in a public issue, pull request, or discussion. Follow
SECURITY.md, which directs reports to GitHub private vulnerability reporting. Before releasing a
security fix, the policy requires the full test suite and dependency audits to pass.