Skip to content

Latest commit

Β 

History

History
319 lines (258 loc) Β· 12.2 KB

File metadata and controls

319 lines (258 loc) Β· 12.2 KB

Authentication Module

Overview

This module provides a tiered authentication system for api-ape WebSocket connections. It supports OPAQUE-based password authentication where the server never learns raw passwords, with MFA (WebAuthn/TOTP) for elevated security and extensibility for enterprise SSO adapters.

Key capabilities:

  • OPAQUE/PAKE authentication β€” Password-authenticated key exchange (server never sees raw password)
  • MFA support β€” WebAuthn (FIDO2) and TOTP (RFC 6238) for Tier 2 elevation
  • Passport.js compatible β€” MFA adapters work with existing Passport.js strategies
  • Tiered security model β€” Guest β†’ Basic β†’ Elevated β†’ High Security
  • State machine enforcement β€” No-downgrade rule, timeout handling, rate limiting
  • Authorization middleware β€” Per-endpoint tier and permission checks
  • Adapter pattern β€” Pluggable authentication methods (OPAQUE, WebAuthn, TOTP, LDAP, SAML, OAuth2)

Contributing? See the module files for implementation details.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     AuthFramework                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Adapter Registry                                             β”‚  β”‚
β”‚  β”‚  - OPAQUE (Tier 1) βœ“                                         β”‚  β”‚
β”‚  β”‚  - WebAuthn (Tier 2 MFA) βœ“                                   β”‚  β”‚
β”‚  β”‚  - TOTP (Tier 2 MFA) βœ“                                       β”‚  β”‚
β”‚  β”‚  - LDAP, SAML, OAuth2 (Tier 1, planned)                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Per-Socket State Machines                                    β”‚  β”‚
β”‚  β”‚  - Tracks auth state per clientId                            β”‚  β”‚
β”‚  β”‚  - Enforces tier requirements                                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Message Router                                               β”‚  β”‚
β”‚  β”‚  - Routes auth messages to appropriate adapter               β”‚  β”‚
β”‚  β”‚  - Handles opaque_*, webauthn_*, totp_*, mfa_* messages     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication Tiers

Tier Name Description
0 GUEST Unauthenticated, public endpoints only
1 BASIC Identity verified via OPAQUE/SRP or enterprise SSO
2 ELEVATED Tier 1 + MFA (WebAuthn or TOTP)
3 HIGH_SECURITY Full 2-of-3 scheme for client-side key reconstruction

Quick Start

1. Create the Auth Framework

const { createAuthFramework } = require('api-ape/server/security/auth');
const { createAuthMiddleware } = require('api-ape/server/socket/authMiddleware');

const authFramework = createAuthFramework({
  opaque: {
    // Provide your user storage functions
    getUser: async (username) => db.users.findOne({ username }),
    saveUser: async (username, data) => db.users.insertOne({ username, ...data })
  },
  webauthn: {
    rpId: 'example.com',
    rpName: 'My App',
    // Optional: provide credential storage
    getCredentials: async (userId) => db.webauthn.find({ userId }),
    saveCredential: async (userId, credential) => db.webauthn.insertOne({ userId, ...credential })
  },
  totp: {
    issuer: 'My App',
    // Optional: provide secret storage
    getSecret: async (userId) => db.totp.findOne({ userId }),
    saveSecret: async (userId, data) => db.totp.upsertOne({ userId }, data)
  },
  mfaMethods: ['webauthn', 'totp'],
  onAuthSuccess: (clientId, principal) => {
    console.log(`${clientId} authenticated as ${principal.userId}`);
  },
  onMFASuccess: (clientId, principal, method) => {
    console.log(`${clientId} elevated via ${method}`);
  }
});

2. Configure Authorization Rules

const authMiddleware = createAuthMiddleware({
  requirements: {
    'admin/*': { tier: 2 },           // Admin endpoints require MFA
    'user/*': { tier: 1 },            // User endpoints require auth
    'public/*': { tier: 0 }           // Public endpoints allow guests
  },
  defaultTier: 0
});

3. Pass to ape()

const { ape } = require('api-ape');

ape(server, {
  where: 'api',
  authFramework,      // Enable authentication
  authMiddleware      // Enable authorization
});

Message Protocol

OPAQUE Registration (Tier 1)

Client                              Server
  |-- opaque_reg_start ----------->|  { user, clientNonce, regRequest }
  |<- opaque_reg_response ---------|  { serverNonce, ts, regResponse }
  |-- opaque_reg_finish ---------->|  { regRecord }
  |<- opaque_reg_ok ---------------|  { msg: "registered" }

OPAQUE Authentication (Tier 1)

Client                              Server
  |-- opaque_auth_start ---------->|  { user, clientNonce }
  |<- opaque_auth_1 ---------------|  { serverNonce, ts, envelope, oprfResponse }
  |-- opaque_auth_2 -------------->|  { clientAuth }
  |<- opaque_auth_ok --------------|  { assignedPrincipal, serverProof, tier: 1 }

WebAuthn Registration (MFA Setup)

Client                              Server
  |-- webauthn_reg_start --------->|  { userId, userName }
  |<- webauthn_reg_challenge ------|  { challenge, rp, user, pubKeyCredParams }
  |-- webauthn_reg_finish -------->|  { challenge, attestation }
  |<- webauthn_reg_ok -------------|  { credentialId }

WebAuthn Authentication (Tier 2 Elevation)

Client                              Server
  |-- webauthn_auth_start -------->|  { userId }
  |<- webauthn_auth_challenge -----|  { challenge, allowCredentials }
  |-- webauthn_auth_finish ------->|  { challenge, assertion }
  |<- webauthn_auth_ok ------------|  { tier: 2 }

TOTP Setup

Client                              Server
  |-- totp_setup_start ----------->|  { userId }
  |<- totp_setup_challenge --------|  { secret, otpauthUri }
  |-- totp_setup_verify ---------->|  { code }
  |<- totp_setup_ok ---------------|  { }

TOTP Verification (Tier 2 Elevation)

Client                              Server
  |-- totp_verify ---------------->|  { userId, code }
  |<- totp_ok --------------------|  { tier: 2 }

Generic MFA Challenge Flow

Client                              Server
  |-- mfa_challenge -------------->|  { }
  |<- mfa_challenge ---------------|  { methods: [{ method: "totp" }, { method: "webauthn", challenge: {...} }] }
  |-- mfa_verify ----------------->|  { method: "totp", code: "123456" }
  |<- mfa_elevated ----------------|  { method: "totp", tier: 2 }

Passport.js Compatibility

Both WebAuthn and TOTP adapters are compatible with Passport.js:

const passport = require('passport');
const { WebAuthnStrategy, TOTPStrategy } = require('api-ape/server/security/auth');

// Use with Passport.js
passport.use('webauthn', new WebAuthnStrategy({
  rpId: 'example.com',
  rpName: 'My App'
}, (user, done) => {
  // Custom verification logic
  done(null, user);
}));

passport.use('totp', new TOTPStrategy({
  issuer: 'My App'
}, (user, done) => {
  // Custom verification logic
  done(null, user);
}));

Controller Context

After authentication, controllers have access to auth state via this:

// api/protected/data.js
module.exports = function(query) {
  // Check authentication
  if (!this.isAuthenticated) {
    throw new Error('Authentication required');
  }

  // Access user info
  console.log('User:', this.principal.userId);
  console.log('Roles:', this.principal.roles);
  console.log('Tier:', this.authTier);

  // Check tier requirement
  if (!this.requiresTier(2)) {
    throw new Error('MFA required for this operation');
  }

  return { data: 'sensitive info' };
};

Available Properties

Property Type Description
this.isAuthenticated boolean Whether socket is authenticated (Tier β‰₯ 1)
this.authTier number Current tier (0-3)
this.principal object|null User info: { userId, roles, permissions }
this.authState object|null Full auth state object
this.requiresTier(n) function Check if socket meets minimum tier

Client Tracking

Query auth state for any connected client:

const client = this.clients.get(targetClientId);

if (client.isAuthenticated) {
  console.log('User:', client.authState.principal.userId);
  console.log('Tier:', client.authTier);
}

State Machine

GUEST β†’ AUTHENTICATING β†’ AUTHENTICATED (Tier 1)
                               β”‚
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚               β”‚               β”‚
         MFA_PENDING    KEY_RECOVERY     (stay Tier 1)
               β”‚               β”‚
               β–Ό               β–Ό
         ELEVATED (2)   HIGH_SECURITY (3)

Rules:

  • No downgrade after authentication
  • Higher tiers require completing lower tiers first
  • Auth timeout closes incomplete sessions
  • Rate limiting and lockout after failed attempts

Security Features

Feature Description
Password protection OPAQUE ensures server never sees raw password
Replay prevention Single-use nonces with 30s expiry
No-downgrade Cannot return to lower tier after auth
Rate limiting Lockout after configurable failed attempts
Session binding Auth bound to clientId + nonces + timestamp
TOTP replay protection Tracks used counters to prevent code reuse
WebAuthn counter Validates and updates authenticator counter

File Structure

auth/
β”œβ”€β”€ index.js              # Auth framework coordinator
β”œβ”€β”€ index.test.js         # Integration tests (23 tests)
β”œβ”€β”€ state-machine.js      # State transitions, tier management
β”œβ”€β”€ state-machine.test.js # State machine tests (31 tests)
β”œβ”€β”€ nonce-manager.js      # Single-use nonce handling
β”œβ”€β”€ adapters/
β”‚   β”œβ”€β”€ opaque.js         # OPAQUE/SRP implementation
β”‚   β”œβ”€β”€ opaque.test.js    # OPAQUE tests (12 tests)
β”‚   β”œβ”€β”€ webauthn.js       # WebAuthn/FIDO2 adapter (Passport.js compatible)
β”‚   β”œβ”€β”€ webauthn.test.js  # WebAuthn tests (25 tests)
β”‚   β”œβ”€β”€ totp.js           # TOTP RFC 6238 adapter (Passport.js compatible)
β”‚   └── totp.test.js      # TOTP tests (35 tests)
└── handlers/
    └── auth-messages.js  # Message routing

Total: 114 tests passing

See Also