Guides

Error Handling & Security

Error codes, security guarantees, and best practices for production deployments of ioserver-oidc.

Error Handling & Security

Error codes

HTTP errors (OidcHttpMiddleware)

CodeHTTP statusMeaning
ERR_AUTH_TOKEN_REQUIRED401Missing Authorization header or value does not start with Bearer
ERR_AUTH_TOKEN_INVALID401JWT signature or claims verification failed (expired, wrong issuer, wrong audience, bad signature)
ERR_USER_DISABLED403Token is valid but the local user account is disabled
ERR_USER_PROVISION_FAILED500users.findOrCreate threw an unexpected error

Response shape:

{
  "statusCode": 401,
  "error": "Unauthorized",
  "code": "ERR_AUTH_TOKEN_REQUIRED",
  "message": "Bearer token required."
}

Socket.IO errors (OidcSocketMiddleware, OidcSocketAdminMiddleware)

Socket.IO errors call next(new Error("ERR_*")), which triggers a connect_error event on the client:

Error messageMeaning
ERR_AUTH_TOKEN_REQUIREDNo token in auth.token or Authorization header
ERR_AUTH_TOKEN_INVALIDJWT verification failure
ERR_USER_DISABLEDUser account is disabled
ERR_FORBIDDENAdmin role required (OidcSocketAdminMiddleware)

Client-side handling:

socket.on("connect_error", (err) => {
  switch (err.message) {
    case "ERR_AUTH_TOKEN_REQUIRED":
      // Redirect to login
      break;
    case "ERR_AUTH_TOKEN_INVALID":
      // Token expired — refresh and reconnect
      break;
    case "ERR_FORBIDDEN":
      // Not an admin — show access-denied UI
      break;
    default:
      console.error("Unexpected connection error:", err.message);
  }
});

Handling token expiry on long-lived connections

JWTs are verified at connection time. A Socket.IO connection that was established with a valid token will remain open even after the token expires — the middleware only runs during the handshake.

To enforce token lifetime on long-lived connections:

  1. Send the token in socket.handshake.auth.token
  2. Before token expiry, have the client reconnect with the refreshed token
  3. Optionally implement a server-side ping that checks socket.features or a timestamp injected at connection time
// Server: record connection time
class AuthService extends BaseService {
  async "connect:init"(socket: any, _data: unknown) {
    socket.connectedAt = Date.now();
  }
}

// Server: periodic check via a Watcher
class TokenWatcher extends BaseWatcher {
  private timer: ReturnType<typeof setInterval> | null = null;

  async watch() {
    this.timer = setInterval(() => {
      // Disconnect sockets connected more than 1 hour ago
      const io = (this.app as any).io;
      io.sockets.sockets.forEach((socket: any) => {
        if (Date.now() - socket.connectedAt > 3_600_000) {
          socket.disconnect(true);
        }
      });
    }, 60_000);
  }

  stop() {
    if (this.timer) { clearInterval(this.timer); this.timer = null; }
  }
}

Security guarantees

Signature verification — Every token is verified against the remote JWKS using jose.jwtVerify. The library accepts RS256 and ES256 by default. Tokens with a symmetric algorithm (HS256) are rejected.

Claim validationiss (issuer) and aud (audience) are always validated. aud must match OidcConfig.appSlug to prevent token substitution between different applications sharing the same auth-service.

Key rotation — JWKS keys are cached per URI. jose automatically re-fetches the key set when signature verification fails with the cached keys, with a minimum 5-minute cooldown to prevent hammering the JWKS endpoint.

No secret storage — Access tokens are never written to disk, a database, or a cache. Verification happens in-memory on every request.

Account disablement — Disabled accounts are checked after token verification, not before. A valid token does not guarantee access — the local users table is the authoritative source for account status.

HTTPS requirement

Tokens transmitted over plain HTTP can be intercepted. Always deploy behind TLS in production. Using Traefik as a reverse proxy with automatic certificate management is a common pattern in this project ecosystem — see the IOServer Docker deployment guide.

Copyright © 2026