Guides

Protecting Routes and Namespaces

Patterns for selectively protecting HTTP routes and Socket.IO namespaces — public vs protected, per-controller granularity.

Protecting Routes and Namespaces

Selective controller protection

Middleware applies at the controller level in IOServer. To have both public and protected routes in the same application, register separate controllers:

// Public routes — no middleware
server.addController({
  name: "public",          // → routes/public.json
  controller: PublicController,
});

// Protected routes — JWT required
server.addController({
  name: "api",             // → routes/api.json
  controller: ApiController,
  middlewares: [OidcHttpMiddleware],
});

routes/public.json:

[
  { "method": "GET", "url": "/health",    "handler": "health"    },
  { "method": "GET", "url": "/directory", "handler": "directory" }
]

routes/api.json:

[
  { "method": "GET",  "url": "/projects",     "handler": "listProjects" },
  { "method": "POST", "url": "/projects",     "handler": "createProject" },
  { "method": "GET",  "url": "/projects/:id", "handler": "getProject"    }
]

Mixed-access Socket.IO namespaces

Always register one service per access level:

// No auth — e.g. public announcements
server.addService({
  name: "announcements",
  service: AnnouncementService,
});

// Any authenticated user
server.addService({
  name: "app",
  service: AppService,
  middlewares: [OidcSocketMiddleware],
});

// Admins only
server.addService({
  name: "panel",
  service: AdminPanelService,
  middlewares: [OidcSocketMiddleware, OidcSocketAdminMiddleware],
});

Checking permissions inside handlers

Beyond the role guard provided by OidcSocketAdminMiddleware, you can implement fine-grained permission checks directly in your handlers:

class ProjectService extends BaseService {
  async deleteProject(socket: any, data: { id: string }, callback?: Function) {
    // Check a specific permission
    if (!socket.permissions.includes("projects.delete")) {
      if (callback) callback({ error: "ERR_FORBIDDEN" });
      return;
    }

    // Proceed with deletion…
    if (callback) callback({ deleted: data.id });
  }
}

For HTTP controllers:

class ProjectController extends BaseController {
  async deleteProject(request: any, reply: any) {
    if (!request.permissions.includes("projects.delete")) {
      return reply.code(403).send({ error: "ERR_FORBIDDEN" });
    }

    reply.code(204).send();
  }
}

Accessing the manager from handlers

OidcConfigManager is a standard IOServer manager — you can access it from any component via this.app:

import { BaseController } from "ioserver";
import { OidcConfigManager } from "ioserver-oidc";

class DebugController extends BaseController {
  async oidcConfig(request: any, reply: any) {
    const mgr = this.app.getManager("oidcConfig") as OidcConfigManager;
    const config = mgr.getConfig();
    reply.send({ authServiceUrl: config.authServiceUrl });
  }
}

Forwarding auth context to downstream services

When your IOServer application calls another internal service, forward the original JWT so downstream services can verify it independently:

class ApiController extends BaseController {
  async getData(request: any, reply: any) {
    const authHeader = request.headers.authorization;

    const result = await fetch("http://internal-service/data", {
      headers: { Authorization: authHeader },
    });

    reply.send(await result.json());
  }
}
Copyright © 2026