Authentication
Operyn uses better-auth in the dashboard app to handle sign-in, sessions, and organization membership. Auth is implemented in a modular way so it can later be moved into a dedicated auth service if needed.
Where Auth Lives
- Dashboard (Next.js) — Auth runs inside the dashboard: an API route at
/api/auth/[...all]delegates to better-auth. Session is cookie-based; the console and all backend calls are made on behalf of the logged-in user. - Backend services (NestJS) —
core-platformand other backend processes do not implement login. They can optionally accept a JWT or user context header from the dashboard so that actions (e.g. “who approved this remediation”) are attributed to a real user. - Future auth service — Auth configuration and session logic are isolated in a single module. When you need a standalone identity service (e.g. for multiple clients or strict compliance), that module can be moved to a new NestJS auth service; the dashboard would then call it over HTTP and keep the same session/JWT contract.
Supported Sign-in Methods
| Method | Purpose |
|---|---|
| Email + password | Default fallback; useful for CLI or service accounts later. |
| Broad adoption; good default for most teams. | |
| GitHub | Fits engineering and ops users. |
| Enterprise SSO | Optional: better-auth’s SSO plugin supports OIDC/SAML (e.g. Okta, Entra ID) for organizations that require central IdP. |
Provider configuration (client IDs, secrets) is done via environment variables and the central auth config in the dashboard.
Email Verification
- Email/password signups require verification — Users who sign up with email and password must verify their inbox before they can complete sign-in.
- Verification emails are sent automatically — Operyn sends a verification link on signup and can resend one from the signup or sign-in screens.
- Verified email is the trust boundary — Email/password accounts only become fully usable after inbox verification, which helps prevent bogus signups and mistaken org access.
- Social sign-in keeps its normal flow — Google and GitHub continue to use the provider’s verified identity flow.
Session and Organization
- Session — better-auth issues a session cookie. Server components and API routes use
getSession()(or equivalent) to get the current user and org context. - Organization plugin — Each user belongs to at least one organization. Organizations have members and roles (e.g. owner, admin, member). This is used for access control and for “who can manage this workspace.”
Organization Access
- Any email can sign up — Users can create an account with either company or personal email addresses.
- Any email can create an organization manually — Personal and unmatched email users can create their own workspace during onboarding.
- Organization creation is manual by default — Work-email users create a workspace themselves unless they were invited or a matching company workspace already exists.
- Pending invites stay visible after signup — If a user already belongs to one organization but still has invitations to others, those invitations remain available from notifications and the organization switcher instead of disappearing behind onboarding.
- Personal or unmatched emails can create their own org manually — They do not get automatic domain-based organization entry, but they can still create a workspace during onboarding.
- Privileged access stays explicit — Higher access still requires owner/admin action through role changes or invitations.
- Access activity is visible in settings — Organization settings include a recent history of invitations, role changes, ownership transfers, and other access-related updates.
Teams vs On‑Call Roster
We intentionally separate organization teams (for structure & permissions) from the on‑call roster (for incidents).
-
Organization Teams (Better Auth)
- Tables:
team,team_member - Code:
- Schema:
libs/shared-types/src/auth.schema.ts,apps/dashboard/lib/auth/schema.ts - Config:
apps/dashboard/lib/auth/config.ts(Better Authorganizationplugin withteams.enabled)
- Schema:
- Purpose:
- Represent teams inside an org (e.g. “Platform”, “SRE”, “Support”).
- Power team switching in the dashboard and team-based permissions.
- Tables:
-
On‑Call Roster (Core Platform incidents module)
- Table:
roster(previouslyteam_members) - Code:
- Schema:
services/core-platform/src/modules/incidents/infrastructure/team.schema.ts - Seed:
services/core-platform/src/seed.ts(SEED_TEAM_MEMBERS) - Usage:
TeamRepository, incident filters, assignee selection
- Schema:
- Purpose:
- List responders who can be assigned to incidents.
- Track
role,designation, andisOnCallper person. - Drive on‑call views and incident assignment, independent of Better Auth’s team membership.
- Table:
Both are governed by the same unified 5‑persona RBAC model, but they serve different layers of the system: org structure vs. incident operations.
Backend User Context (Optional)
So that core incident and remediation flows can record “who did this” (e.g. who approved a remediation), the dashboard can send a JWT in the Authorization header when calling backend APIs. core-platform uses a shared JWT guard that validates the token and attaches user id/role to the request. The JWT payload (e.g. sub, email, orgId, role) is defined in @operyn/shared-types so every module shares the same contract.
Related Docs
- Roles and permissions — Personas, permission list, and role–permission matrix.
- Auth setup guide — Environment variables, provider setup, and migration steps.