CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-passport-oauth2

OAuth 2.0 authentication strategy for Passport that enables developers to implement OAuth 2.0-based authentication in Node.js applications.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

state-management.mddocs/

State Management

Passport OAuth2 provides multiple state store implementations for CSRF protection and PKCE (Proof Key for Code Exchange) support, enabling secure OAuth 2.0 flows with various storage backends.

Capabilities

StateStore (Full State Store)

Full session-based state store implementation that provides CSRF protection by storing state parameters and metadata in the user session.

/**
 * Full session-based state store constructor (from lib/state/store)
 * @param options - Configuration options
 */
function StateStore(options);

interface SessionStoreOptions {
  key: string; // Required: Session key for storing state data
}

/**
 * Store state in session
 * @param req - HTTP request object with session
 * @param state - State value to store
 * @param meta - Metadata about the OAuth request
 * @param callback - Completion callback
 */
StateStore.prototype.store = function(req, state, meta, callback);

/**
 * Verify provided state against stored state
 * @param req - HTTP request object with session  
 * @param providedState - State parameter from OAuth callback
 * @param callback - Verification callback
 */
StateStore.prototype.verify = function(req, providedState, callback);

Usage Example:

const StateStore = require('passport-oauth2/lib/state/store');

// Create session store
const store = new StateStore({ key: 'oauth2:state' });

// Usage in strategy
const strategy = new OAuth2Strategy({
  authorizationURL: 'https://example.com/oauth/authorize',
  tokenURL: 'https://example.com/oauth/token',
  clientID: 'your-client-id',
  clientSecret: 'your-client-secret',
  store: store // Use custom state store
}, verifyCallback);

SessionStore (Simple Session Store)

Simplified session-based state store that generates random nonces for CSRF protection without additional metadata. This is the implementation from lib/state/session.js.

/**
 * Simple session-based state store constructor (from lib/state/session)
 * @param options - Configuration options
 */
function SessionStore(options);

/**
 * Store random nonce in session
 * @param req - HTTP request object with session
 * @param callback - Completion callback receiving generated state
 */
SessionStore.prototype.store = function(req, callback);

/**
 * Verify provided state against stored nonce
 * @param req - HTTP request object with session
 * @param providedState - State parameter from OAuth callback
 * @param callback - Verification callback
 */
SessionStore.prototype.verify = function(req, providedState, callback);

Implementation Details:

  • Generates 24-character random state using uid2
  • Stores state directly in session under configured key
  • Automatically cleans up session data after verification
  • Requires Express session middleware

Usage Example:

const strategy = new OAuth2Strategy({
  authorizationURL: 'https://example.com/oauth/authorize',
  tokenURL: 'https://example.com/oauth/token',
  clientID: 'your-client-id',
  clientSecret: 'your-client-secret',
  state: true // Automatically uses SessionStore from lib/state/session
}, verifyCallback);

PKCESessionStore

PKCE-enabled session state store that supports Proof Key for Code Exchange along with state management for enhanced security.

/**
 * PKCE-enabled session state store constructor
 * @param options - Configuration options
 */  
function PKCESessionStore(options);

/**
 * Store PKCE verifier and state in session
 * @param req - HTTP request object with session
 * @param verifier - PKCE code verifier
 * @param state - State value to store  
 * @param meta - Metadata about the OAuth request
 * @param callback - Completion callback receiving state handle
 */
PKCESessionStore.prototype.store = function(req, verifier, state, meta, callback);

/**
 * Verify state and return PKCE code verifier
 * @param req - HTTP request object with session
 * @param providedState - State parameter from OAuth callback
 * @param callback - Verification callback receiving verifier
 */
PKCESessionStore.prototype.verify = function(req, providedState, callback);

PKCE Flow:

  1. Strategy generates code_verifier (random 32-byte value, base64url encoded)
  2. Strategy creates code_challenge using SHA256 hash of verifier (for S256 method)
  3. PKCESessionStore saves verifier with state handle in session
  4. Authorization request includes code_challenge and code_challenge_method
  5. Token request includes code_verifier retrieved from session

Usage Example:

const strategy = new OAuth2Strategy({
  authorizationURL: 'https://example.com/oauth/authorize',
  tokenURL: 'https://example.com/oauth/token',
  clientID: 'your-client-id',
  clientSecret: 'your-client-secret',
  pkce: 'S256', // Enable PKCE with SHA256
  state: true   // Automatically uses PKCESessionStore when PKCE enabled
}, verifyCallback);

NullStore

No-operation state store that performs no state validation, effectively disabling CSRF protection.

/**
 * No-op state store constructor
 * @param options - Configuration options (unused)
 */
function NullStore(options);

/**
 * No-op store method
 * @param req - HTTP request object
 * @param callback - Completion callback
 */
NullStore.prototype.store = function(req, callback);

/**
 * No-op verify method that always succeeds
 * @param req - HTTP request object
 * @param providedState - State parameter (ignored)
 * @param callback - Verification callback (always returns true)
 */
NullStore.prototype.verify = function(req, providedState, callback);

Security Warning: NullStore provides no CSRF protection and should only be used in development or when state validation is handled externally.

Usage Example:

const strategy = new OAuth2Strategy({
  authorizationURL: 'https://example.com/oauth/authorize',
  tokenURL: 'https://example.com/oauth/token', 
  clientID: 'your-client-id',
  clientSecret: 'your-client-secret',
  // No state option = NullStore used automatically
}, verifyCallback);

State Store Configuration

The OAuth2Strategy automatically selects the appropriate state store based on configuration options:

// State store selection logic in OAuth2Strategy
if (options.store && typeof options.store === 'object') {
  this._stateStore = options.store; // Custom store instance
} else if (options.store) {
  // Use built-in stores with session key
  this._stateStore = options.pkce 
    ? new PKCESessionStore({ key: this._key })
    : new StateStore({ key: this._key }); // Full state store from lib/state/store
} else if (options.state) {
  // Enable state with appropriate store
  this._stateStore = options.pkce
    ? new PKCESessionStore({ key: this._key })  
    : new SessionStore({ key: this._key }); // Simple session store from lib/state/session
} else {
  // No state protection
  if (options.pkce) {
    throw new TypeError('OAuth2Strategy requires `state: true` option when PKCE is enabled');
  }
  this._stateStore = new NullStore();
}

Custom State Store Implementation

You can implement custom state stores by providing an object with store and verify methods:

/**
 * Custom state store interface
 */
interface CustomStateStore {
  /**
   * Store state data
   * @param req - HTTP request object
   * @param state - State to store (or verifier for PKCE)
   * @param meta - OAuth request metadata
   * @param callback - Completion callback
   */
  store(req: any, state?: string, meta?: any, callback?: Function): void;
  
  /**
   * Verify stored state
   * @param req - HTTP request object
   * @param providedState - State from OAuth callback
   * @param callback - Verification callback
   */
  verify(req: any, providedState: string, callback: Function): void;
}

Custom Redis Store Example:

const redis = require('redis');
const client = redis.createClient();

class RedisStateStore {
  constructor(options) {
    this.prefix = options.prefix || 'oauth2:state:';
    this.ttl = options.ttl || 300; // 5 minutes
  }

  store(req, state, meta, callback) {
    const key = this.prefix + require('uid2')(24);
    const data = JSON.stringify({ state, meta, timestamp: Date.now() });
    
    client.setex(key, this.ttl, data, (err) => {
      if (err) return callback(err);
      callback(null, key);
    });
  }

  verify(req, providedState, callback) {
    client.get(providedState, (err, data) => {
      if (err) return callback(err);
      if (!data) return callback(null, false, { message: 'Invalid state' });
      
      client.del(providedState); // Clean up
      
      try {
        const parsed = JSON.parse(data);
        callback(null, true, parsed.state);
      } catch (e) {
        callback(null, false, { message: 'Invalid state format' });
      }
    });
  }
}

// Usage
const strategy = new OAuth2Strategy({
  // ... other options
  store: new RedisStateStore({ prefix: 'myapp:oauth:' })
}, verifyCallback);

Session Requirements

Session-based state stores require session middleware to be configured:

const session = require('express-session');

app.use(session({
  secret: 'your-session-secret',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } // Set to true in production with HTTPS
}));

Common Session Errors:

  • OAuth 2.0 authentication requires session support when using state: Session middleware not configured
  • Unable to verify authorization request state: Session data missing or corrupted
  • Invalid authorization request state: State parameter mismatch

Install with Tessl CLI

npx tessl i tessl/npm-passport-oauth2

docs

error-handling.md

index.md

state-management.md

tile.json