OAuth2 Server for Node.js

3.1.1 · active · verified Tue Apr 21

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).

Common errors

Warnings

Install

Imports

Quickstart

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.

import express from 'express';
import OAuth2Server, { Request, Response } from 'oauth2-server';
import bodyParser from 'body-parser';

// A minimalistic in-memory model for demonstration purposes.
const model = {
  // Client storage
  clients: [{
    id: 'client1',
    secret: 'clientsecret',
    redirectUris: ['http://localhost:3000/oauth/callback'],
    grants: ['authorization_code', 'refresh_token', 'password', 'client_credentials'],
  }],
  // Token/Code storage
  tokens: [],
  authorizationCodes: [],

  // Required: getClient(clientId, clientSecret, callback)
  async getClient(clientId, clientSecret) {
    const client = this.clients.find(c => c.id === clientId);
    if (!client) return null;
    if (clientSecret && client.secret !== clientSecret) return null;
    return client;
  },

  // Required for authorization_code grant
  async saveAuthorizationCode(code, client, user) {
    this.authorizationCodes.push({ code: code.authorizationCode, expiresAt: code.expiresAt, redirectUri: code.redirectUri, scope: code.scope, client: client, user: user });
    return code;
  },
  async getAuthorizationCode(authorizationCode) {
    const code = this.authorizationCodes.find(c => c.code === authorizationCode && c.expiresAt > new Date());
    if (!code) return null;
    // In a real app, delete the code after use: this.authorizationCodes = this.authorizationCodes.filter(c => c.code !== authorizationCode);
    return code;
  },
  async revokeAuthorizationCode(code) {
    this.authorizationCodes = this.authorizationCodes.filter(c => c.code !== code.authorizationCode);
    return true;
  },

  // Required for password, client_credentials, refresh_token grants
  async saveToken(token, client, user) {
    this.tokens.push({ accessToken: token.accessToken, accessTokenExpiresAt: token.accessTokenExpiresAt, refreshToken: token.refreshToken, refreshTokenExpiresAt: token.refreshTokenExpiresAt, scope: token.scope, client: client, user: user });
    return token;
  },
  async getAccessToken(accessToken) {
    return this.tokens.find(t => t.accessToken === accessToken && t.accessTokenExpiresAt > new Date());
  },
  async getRefreshToken(refreshToken) {
    return this.tokens.find(t => t.refreshToken === refreshToken && t.refreshTokenExpiresAt > new Date());
  },
  async revokeToken(token) {
    this.tokens = this.tokens.filter(t => t.refreshToken !== token.refreshToken);
    return true;
  },

  // Required for password grant
  async getUser(username, password) {
    // Dummy user for 'password' grant
    if (username === 'testuser' && password === 'testpass') {
      return { id: '123', username: 'testuser' };
    }
    return null;
  },

  // Optional: verifyScope(token, scope)
  async verifyScope(token, scope) {
    if (!scope) return true;
    const requestedScopes = scope.split(' ');
    const authorizedScopes = token.scope.split(' ');
    return requestedScopes.every(s => authorizedScopes.includes(s));
  }
};

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

const oauth = new OAuth2Server({
  model: model,
  accessTokenLifetime: 60 * 60, // 1 hour
  allowBearerTokensInQueryString: true, // Only for testing, not recommended for production.
});

// Grant token endpoint (e.g., /oauth/token for password, client_credentials, refresh_token grants)
app.post('/oauth/token', async (req, res) => {
  try {
    const token = await oauth.token(new Request(req), new Response(res));
    res.json(token);
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).json(err);
  }
});

// Authorize endpoint (e.g., /oauth/authorize for authorization_code grant)
app.get('/oauth/authorize', async (req, res) => {
  // In a real application, render an authorization screen here
  // For this example, we'll auto-approve if 'user' query param is present
  if (req.query.response_type === 'code' && req.query.client_id && req.query.redirect_uri) {
    if (req.query.user === 'approved') {
      const authRequest = new Request(req);
      const authResponse = new Response(res);
      try {
        const code = await oauth.authorize(authRequest, authResponse, {
          authenticateHandler: { handle: async () => ({ id: '123', username: 'testuser' }) }
        });
        res.redirect(code.redirectUri + '?code=' + code.authorizationCode);
      } catch (err) {
        console.error(err);
        res.status(err.code || 500).json(err);
      }
    } else {
      // Simulate a login/consent page redirect
      res.send(`
        <h1>Authorize Client</h1>
        <p>Client ${req.query.client_id} wants access to your data.</p>
        <form action="/oauth/authorize" method="GET">
          <input type="hidden" name="response_type" value="${req.query.response_type}">
          <input type="hidden" name="client_id" value="${req.query.client_id}">
          <input type="hidden" name="redirect_uri" value="${req.query.redirect_uri}">
          <input type="hidden" name="scope" value="${req.query.scope || ''}">
          <button type="submit" name="user" value="approved">Approve</button>
        </form>
      `);
    }
  } else {
    res.status(400).json({ error: 'invalid_request', error_description: 'Missing required parameters for authorization.' });
  }
});

// Protected resource endpoint
app.get('/protected', async (req, res) => {
  try {
    const token = await oauth.authenticate(new Request(req), new Response(res));
    res.json({ message: 'Hello, ' + token.user.username + '!', tokenInfo: token });
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).json(err);
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`OAuth2 server listening on port ${PORT}`);
  console.log('Try POST to /oauth/token with grant_type=password, username=testuser, password=testpass, client_id=client1, client_secret=clientsecret');
  console.log('Try GET to /oauth/authorize with ?response_type=code&client_id=client1&redirect_uri=http://localhost:3000/oauth/callback&scope=read');
  console.log('Then navigate to /protected with Authorization: Bearer <access_token>');
});

view raw JSON →