Skip to main content
The @ooneex/auth component handles authentication: it verifies bearer tokens, resolves the authenticated user, and enforces role-based route protection. It exposes an IAuth interface and a @decorator.auth() decorator so you can register any strategy with the container — a local JWT, an OAuth provider, or a third-party identity service.

Why this component

  • Token verification built in. Extract a bearer token, verify it, and resolve the current user without wiring the pipeline yourself.
  • Role-aware routing. Middleware reads each route’s roles and only enforces authentication on protected routes — guest routes pass through.
  • Pluggable strategies. Implement IAuth to back authentication with any provider while call sites stay unchanged.
  • Container-managed. Register with a decorator and resolve from the container — no manual wiring.
  • Provider-agnostic call sites. Controllers read context.user and never depend on the underlying auth provider.

How it works

Authentication runs as middleware in the request pipeline. For each request it:
  1. Extracts the token from the Authorization: Bearer <token> header (or a bearerToken query).
  2. Checks the route’s roles — if the route is guest-only (no roles or ROLE_GUEST), it skips verification.
  3. Otherwise verifies the token and resolves the user via getCurrentUser(token).
  4. Maps the provider’s user onto the framework IUser and sets it on context.user.
Request → Bearer token
  → Auth middleware: route requires roles?
      → guest-only  → continue, no token needed
      → protected   → verify token → getCurrentUser(token) → context.user
  → Controller (context.user is available)
Downstream controllers read context.user — they never import the auth provider directly.

Decorator and usage

@decorator.auth()

Registers an auth class with the container. It accepts an optional scope (defaults to singleton). A class only needs to implement getCurrentUser() to satisfy IAuth.
import { decorator } from "@ooneex/auth";
import type { IAuth } from "@ooneex/auth";

@decorator.auth()
export class JwtAuth implements IAuth {
  public async getCurrentUser(token?: string): Promise<unknown> {
    if (!token) return null;
    // Verify and decode the token, then return the user.
    return verifyAndDecode(token);
  }
}
Once registered, the auth middleware uses your strategy to resolve context.user. You can also resolve the strategy from the container directly:
import { container } from "@ooneex/container";

const auth = container.get(JwtAuth);
const user = await auth.getCurrentUser(token);

Protecting routes

Declare the roles a route requires in its Route decorator. The middleware enforces authentication only when roles are present; an empty list (or ROLE_GUEST) leaves the route open.
import { Route } from "@ooneex/routing";

@Route.get("/api/profile", {
  name: "profile",
  version: 1,
  description: "Get the current user profile",
  roles: ["ROLE_USER"], // requires a valid token
})
export class ProfileController {
  public async index(context: ContextType<ProfileRouteType>) {
    const { user } = context; // set by the auth middleware
    return context.response.json({
      id: user.id,
      email: user.email,
      roles: user.roles,
    });
  }
}
A guest route omits roles, so no token is required:
@Route.post("/api/login", {
  name: "login",
  version: 1,
  description: "Sign in with email and password",
  roles: [], // guest-only — token not required
})
export class LoginController {
  public async index(context: ContextType<LoginRouteType>) {
    // ...
  }
}

Exceptions

The component throws AuthException for authentication failures. It carries a machine-readable key, a human-readable message, and a data object, so callers can branch on the key.
KeyWhen
MISSING_BEARER_TOKENA protected route is requested without a token.
INVALID_TOKENToken verification fails or resolves no user.
import { AuthException } from "@ooneex/auth";

try {
  const user = await auth.getCurrentUser(token);
  return context.response.json({ user });
} catch (error) {
  if (error instanceof AuthException && error.key === "INVALID_TOKEN") {
    return context.response
      .status(401)
      .json({ error: "Invalid or expired token" });
  }
  throw error;
}

Best practices

  • Drive access from route roles. Declare roles on routes and let the middleware enforce them — don’t re-check tokens inside controllers.
  • Read context.user, not the provider. Keep controllers provider-agnostic so the auth strategy can change without touching handlers.
  • Branch on AuthException.key. Return 401 for MISSING_BEARER_TOKEN / INVALID_TOKEN; keep keys stable and put detail in data.
  • Keep verification secrets in the environment. Load any signing key or provider secret from .env; never hard-code it.
  • Fail fast on misconfiguration. Validate required secrets when the strategy is constructed so problems surface at startup, not at request time.
  • Implement IAuth to swap strategies. Back a custom provider by implementing getCurrentUser() and registering it with @decorator.auth().