User Provisioning
User Provisioning
Both OidcHttpMiddleware and OidcSocketMiddleware support automatic local user provisioning. When a valid JWT is received, the middleware checks whether appHandle.users.findOrCreate is available and calls it with the OIDC subject to create or retrieve the local user record.
How it works
JWT verified → OidcUserContext { sub, email, name, … }
│
▼
appHandle.users.findOrCreate(sub, { email, name })
│
├─ User exists → return { id, role, isDisabled }
└─ User missing → create record → return { id, role, isDisabled }
│
▼
isDisabled === true → 403 ERR_USER_DISABLED
isDisabled === false → inject userId, userRole, … → proceed
The userId injected onto request / socket is the local DB user ID returned by findOrCreate — not the OIDC sub. The OIDC sub is always available separately as request.sub / socket.sub.
Implementing the users manager
Create a BaseManager subclass named "users" and expose a findOrCreate method with the following signature (shape must match what the middlewares call):
import { BaseManager } from "ioserver";
interface LocalUser {
id: string;
role: string;
isDisabled: boolean;
}
class UsersManager extends BaseManager {
async findOrCreate(
sub: string,
profile: { email: string | null; name: string | null },
): Promise<LocalUser> {
// Look up the user by their OIDC sub claim
let user = await db.users.findOne({ sub });
if (!user) {
// First access: create a new local record
user = await db.users.create({
sub,
email: profile.email,
name: profile.name,
role: "user",
isDisabled: false,
});
}
return {
id: user.id,
role: user.role,
isDisabled: user.isDisabled,
};
}
}
// Register BEFORE the OIDC middlewares are invoked
server.addManager({ name: "users", manager: UsersManager });
Without a users manager
If no users manager is registered (or findOrCreate is not defined on it), the middlewares skip provisioning entirely. In that case userId is set to the OIDC sub value — which is still a stable, unique identifier, just not a local DB primary key.
This is useful during prototyping or when your application does not maintain a local users table.
Disabling accounts
To disable a user, set isDisabled: true in your local DB. On the next request or connection attempt the middleware will reject with 403 / ERR_USER_DISABLED even if the JWT is still valid. This provides instant revocation without waiting for the token to expire.
// Disable a user instantly
await db.users.update({ sub }, { isDisabled: true });
// Their next request will receive 403 before reaching any handler
Session tracking
OidcSocketMiddleware also calls appHandle.session.registerSocket(userId, socketId, sub) if available. Implement a "session" manager exposing that method to track active WebSocket connections:
class SessionManager extends BaseManager {
private sockets = new Map<string, { userId: string; sub: string }>();
registerSocket(userId: string, socketId: string, sub: string) {
this.sockets.set(socketId, { userId, sub });
}
unregisterSocket(socketId: string) {
this.sockets.delete(socketId);
}
getActiveSockets() {
return [...this.sockets.entries()];
}
}
server.addManager({ name: "session", manager: SessionManager });