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());
}
}