{"id":15739,"library":"oauth2-server","title":"OAuth2 Server for Node.js","description":"oauth2-server is a complete, framework-agnostic, and well-tested module for implementing an OAuth2 Authorization Server in Node.js. It adheres to RFC 6749 (OAuth 2.0 Authorization Framework) and RFC 6750 (Bearer Token Usage), providing the core logic for handling various OAuth 2.0 grant types including `authorization_code`, `client_credentials`, `refresh_token`, and `password` grants, as well as support for custom extension grants and scopes. The library is currently at version 3.1.1 and is under active maintenance, with recent releases focusing on bug fixes and dependency updates after a period of hiatus. It distinguishes itself by offering a robust, compliant foundation that can be integrated with any Node.js HTTP framework (like Express or Koa via official wrappers), supporting promises, Node-style callbacks, and async/await for model interactions. It doesn't dictate a specific storage mechanism, allowing developers to plug in their preferred database (e.g., PostgreSQL, MongoDB, Redis).","status":"active","version":"3.1.1","language":"javascript","source_language":"en","source_url":"https://github.com/oauthjs/node-oauth2-server","tags":["javascript","oauth","oauth2"],"install":[{"cmd":"npm install oauth2-server","lang":"bash","label":"npm"},{"cmd":"yarn add oauth2-server","lang":"bash","label":"yarn"},{"cmd":"pnpm add oauth2-server","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"The primary class. While CommonJS `require` works, the module is designed for ES modules. If using CommonJS, it's typically imported as a default, not a named export from the root.","wrong":"const OAuth2Server = require('oauth2-server').OAuth2Server;","symbol":"OAuth2Server","correct":"import OAuth2Server from 'oauth2-server';"},{"note":"These are classes that wrap native Node.js request and response objects, enabling framework-agnostic operations. Ensure you use the exact names `Request` and `Response`.","wrong":"import { OAuth2Request, OAuth2Response } from 'oauth2-server';","symbol":"Request, Response","correct":"import { Request, Response } from 'oauth2-server';"},{"note":"For Express.js applications, it's highly recommended to use the official `@oauthjs/express-oauth-server` wrapper for easier integration, rather than manually wiring `oauth2-server` middleware.","wrong":"import OAuth2Server from 'oauth2-server'; // Directly with Express","symbol":"express-oauth-server (wrapper)","correct":"import OAuth2Server from '@oauthjs/express-oauth-server';"}],"quickstart":{"code":"import express from 'express';\nimport OAuth2Server, { Request, Response } from 'oauth2-server';\nimport bodyParser from 'body-parser';\n\n// A minimalistic in-memory model for demonstration purposes.\nconst model = {\n  // Client storage\n  clients: [{\n    id: 'client1',\n    secret: 'clientsecret',\n    redirectUris: ['http://localhost:3000/oauth/callback'],\n    grants: ['authorization_code', 'refresh_token', 'password', 'client_credentials'],\n  }],\n  // Token/Code storage\n  tokens: [],\n  authorizationCodes: [],\n\n  // Required: getClient(clientId, clientSecret, callback)\n  async getClient(clientId, clientSecret) {\n    const client = this.clients.find(c => c.id === clientId);\n    if (!client) return null;\n    if (clientSecret && client.secret !== clientSecret) return null;\n    return client;\n  },\n\n  // Required for authorization_code grant\n  async saveAuthorizationCode(code, client, user) {\n    this.authorizationCodes.push({ code: code.authorizationCode, expiresAt: code.expiresAt, redirectUri: code.redirectUri, scope: code.scope, client: client, user: user });\n    return code;\n  },\n  async getAuthorizationCode(authorizationCode) {\n    const code = this.authorizationCodes.find(c => c.code === authorizationCode && c.expiresAt > new Date());\n    if (!code) return null;\n    // In a real app, delete the code after use: this.authorizationCodes = this.authorizationCodes.filter(c => c.code !== authorizationCode);\n    return code;\n  },\n  async revokeAuthorizationCode(code) {\n    this.authorizationCodes = this.authorizationCodes.filter(c => c.code !== code.authorizationCode);\n    return true;\n  },\n\n  // Required for password, client_credentials, refresh_token grants\n  async saveToken(token, client, user) {\n    this.tokens.push({ accessToken: token.accessToken, accessTokenExpiresAt: token.accessTokenExpiresAt, refreshToken: token.refreshToken, refreshTokenExpiresAt: token.refreshTokenExpiresAt, scope: token.scope, client: client, user: user });\n    return token;\n  },\n  async getAccessToken(accessToken) {\n    return this.tokens.find(t => t.accessToken === accessToken && t.accessTokenExpiresAt > new Date());\n  },\n  async getRefreshToken(refreshToken) {\n    return this.tokens.find(t => t.refreshToken === refreshToken && t.refreshTokenExpiresAt > new Date());\n  },\n  async revokeToken(token) {\n    this.tokens = this.tokens.filter(t => t.refreshToken !== token.refreshToken);\n    return true;\n  },\n\n  // Required for password grant\n  async getUser(username, password) {\n    // Dummy user for 'password' grant\n    if (username === 'testuser' && password === 'testpass') {\n      return { id: '123', username: 'testuser' };\n    }\n    return null;\n  },\n\n  // Optional: verifyScope(token, scope)\n  async verifyScope(token, scope) {\n    if (!scope) return true;\n    const requestedScopes = scope.split(' ');\n    const authorizedScopes = token.scope.split(' ');\n    return requestedScopes.every(s => authorizedScopes.includes(s));\n  }\n};\n\nconst app = express();\napp.use(bodyParser.json());\napp.use(bodyParser.urlencoded({ extended: false }));\n\nconst oauth = new OAuth2Server({\n  model: model,\n  accessTokenLifetime: 60 * 60, // 1 hour\n  allowBearerTokensInQueryString: true, // Only for testing, not recommended for production.\n});\n\n// Grant token endpoint (e.g., /oauth/token for password, client_credentials, refresh_token grants)\napp.post('/oauth/token', async (req, res) => {\n  try {\n    const token = await oauth.token(new Request(req), new Response(res));\n    res.json(token);\n  } catch (err) {\n    console.error(err);\n    res.status(err.code || 500).json(err);\n  }\n});\n\n// Authorize endpoint (e.g., /oauth/authorize for authorization_code grant)\napp.get('/oauth/authorize', async (req, res) => {\n  // In a real application, render an authorization screen here\n  // For this example, we'll auto-approve if 'user' query param is present\n  if (req.query.response_type === 'code' && req.query.client_id && req.query.redirect_uri) {\n    if (req.query.user === 'approved') {\n      const authRequest = new Request(req);\n      const authResponse = new Response(res);\n      try {\n        const code = await oauth.authorize(authRequest, authResponse, {\n          authenticateHandler: { handle: async () => ({ id: '123', username: 'testuser' }) }\n        });\n        res.redirect(code.redirectUri + '?code=' + code.authorizationCode);\n      } catch (err) {\n        console.error(err);\n        res.status(err.code || 500).json(err);\n      }\n    } else {\n      // Simulate a login/consent page redirect\n      res.send(`\n        <h1>Authorize Client</h1>\n        <p>Client ${req.query.client_id} wants access to your data.</p>\n        <form action=\"/oauth/authorize\" method=\"GET\">\n          <input type=\"hidden\" name=\"response_type\" value=\"${req.query.response_type}\">\n          <input type=\"hidden\" name=\"client_id\" value=\"${req.query.client_id}\">\n          <input type=\"hidden\" name=\"redirect_uri\" value=\"${req.query.redirect_uri}\">\n          <input type=\"hidden\" name=\"scope\" value=\"${req.query.scope || ''}\">\n          <button type=\"submit\" name=\"user\" value=\"approved\">Approve</button>\n        </form>\n      `);\n    }\n  } else {\n    res.status(400).json({ error: 'invalid_request', error_description: 'Missing required parameters for authorization.' });\n  }\n});\n\n// Protected resource endpoint\napp.get('/protected', async (req, res) => {\n  try {\n    const token = await oauth.authenticate(new Request(req), new Response(res));\n    res.json({ message: 'Hello, ' + token.user.username + '!', tokenInfo: token });\n  } catch (err) {\n    console.error(err);\n    res.status(err.code || 500).json(err);\n  }\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n  console.log(`OAuth2 server listening on port ${PORT}`);\n  console.log('Try POST to /oauth/token with grant_type=password, username=testuser, password=testpass, client_id=client1, client_secret=clientsecret');\n  console.log('Try GET to /oauth/authorize with ?response_type=code&client_id=client1&redirect_uri=http://localhost:3000/oauth/callback&scope=read');\n  console.log('Then navigate to /protected with Authorization: Bearer <access_token>');\n});\n","lang":"javascript","description":"This quickstart sets up a basic OAuth2 server using Express.js and `oauth2-server`, demonstrating `password`, `client_credentials`, `refresh_token`, and `authorization_code` grants with an in-memory data model. It includes token and authorization endpoints, and a protected resource. This minimal model is for illustration; a real application requires persistent storage."},"warnings":[{"fix":"Consult the official 'Migrating from 2.x to 3.x' guide in the `oauth2-server` documentation. Re-evaluate server options and adapt your model's method signatures and return types to align with the v3 specification.","message":"Migrating from `oauth2-server` 2.x to 3.x involves significant breaking changes. Several server options, such as `grants`, `debug`, `clientIdRegex`, `passthroughErrors`, and `continueAfterResponse`, have been removed. The model specification has also changed, requiring updates to your implementation of methods like `getAccessToken` and `generateAccessToken`. The module now primarily leverages Promises for asynchronous operations, though it retains compatibility with Node-style callbacks, ES6 generators, and async/await.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Design your application to comply with OAuth 2.1 best practices: use Authorization Code Grant with PKCE for all client types, avoid ROPC and Implicit grants, enforce strict redirect URI validation, and transmit tokens only in the `Authorization` header. `oauth2-server` provides the building blocks, but you must configure your implementation to follow these security standards.","message":"While `oauth2-server` provides a robust implementation of OAuth 2.0 (RFC 6749/6750), the newer OAuth 2.1 standard (a consolidation of security best practices) deprecates several older, insecure flows and introduces new requirements. Notably, the Implicit Grant and Resource Owner Password Credentials (ROPC) flows are removed, and Proof Key for Code Exchange (PKCE) is mandatory for all public clients. OAuth 2.1 also strictly prohibits passing bearer tokens in URL query parameters and requires exact matching for redirect URIs.","severity":"gotcha","affected_versions":">=3.0.0"},{"fix":"Always enforce strict, exact matching for registered `redirect_uri` values. Ensure all redirect URIs are pre-registered and validated against an allow-list on your authorization server. Never accept arbitrary redirect URIs.","message":"Improper validation of `redirect_uri` can lead to critical security vulnerabilities, including authorization code or token leakage, allowing attackers to hijack user accounts. Overly permissive redirect URIs (e.g., using wildcards or prefix matching) or typos can be exploited.","severity":"gotcha","affected_versions":">=3.0.0"},{"fix":"Generate a unique, cryptographically secure random string for the `state` parameter for each authorization request. Store this state securely (e.g., in a session or cookie) and verify it upon receiving the callback from the authorization server to ensure the response belongs to the original request.","message":"The `state` parameter in OAuth 2.0 is crucial for Cross-Site Request Forgery (CSRF) protection. If it's missing, weak (predictable), or not properly validated by the client application, it creates an attack vector where an attacker can complete the authorization flow on behalf of a victim.","severity":"gotcha","affected_versions":">=3.0.0"},{"fix":"Thoroughly review the `oauth2-server` model specification and ensure all required methods for your chosen grant types are implemented correctly, returning the expected data formats. Use console logging and unit tests for your model implementation to catch issues early.","message":"Misconfigurations within the `model` object, which defines how `oauth2-server` interacts with your data store, are a common source of runtime errors and incorrect behavior. Forgetting to implement a required method (e.g., `getClient`, `saveToken`) or returning an invalid data structure can lead to `invalid_request` or `server_error` responses.","severity":"gotcha","affected_versions":">=3.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Ensure your `model` object passed to `new OAuth2Server()` includes an asynchronous function `getAccessToken(accessToken)` that returns a token object if valid, or `null` otherwise. Review the model specification for all required methods based on your enabled grants.","cause":"The configured OAuth2Server model object is missing a required method, `getAccessToken`, or it's not correctly defined. This method is fundamental for authenticating bearer tokens.","error":"Error: model must implement getAccessToken()"},{"fix":"For authorization codes, ensure they are single-use and quickly invalidated after first use. Verify expiry times for all tokens. Double-check that the `client_id` and `client_secret` used for token exchange match the client that initiated the authorization flow. Inspect logs for more specific reasons (e.g., 'code expired').","cause":"This error often occurs when an authorization code is used more than once, has expired, or was issued to a different client than the one attempting to exchange it. It can also happen if the refresh token is invalid or revoked.","error":"OAuth2Error: invalid_grant (The provided authorization grant is invalid, expired, revoked, or was issued to another client.)"},{"fix":"Ensure the `redirect_uri` parameter in the client's authorization request is an exact string match for one of the `redirectUris` associated with the `client_id` in your OAuth2 model. All valid `redirectUris` should be explicitly listed and strictly validated.","cause":"The `redirect_uri` sent by the client in the authorization request does not exactly match one of the `redirectUris` configured for that client in your OAuth2 server's model. Even subtle differences, like a trailing slash or case, can cause this.","error":"OAuth2Error: redirect_uri_mismatch (The redirection URI provided does not match a pre-registered redirection URI for the application.)"},{"fix":"If your project is ES Module-based, use `import OAuth2Server from 'oauth2-server';`. If your project is CommonJS, use `const OAuth2Server = require('oauth2-server');`. Ensure your `package.json`'s `type` field and module syntax are consistent. Consider using a bundler like Webpack or Rollup for complex module environments.","cause":"You are attempting to use the CommonJS `require()` syntax to import `oauth2-server` (or its wrappers) in a Node.js project configured for ES Modules (`\"type\": \"module\"` in `package.json`), or vice versa, in a way that Node.js cannot reconcile.","error":"ERR_REQUIRE_ESM: require() of ES Module ... not supported."}],"ecosystem":"npm"}