Authorization header.
Authentication Overview
OpenInsure uses a dedicated Auth Worker (oi-sys-auth) running on Cloudflare Workers with Cloudflare D1 (edge SQLite) as the identity store. User accounts, sessions, and organization memberships are managed in the D1 database oi-auth:
user— User accounts (id,email,role,orgId)session— Active sessionsaccount— OAuth/provider accounts (Microsoft SSO)verification— Email/phone verifications
https://auth-dev.openinsure.dev.
Sign-In Methods
Email / Password
oi_session cookie that portal middleware reads automatically.
Microsoft SSO (mhcis.com Tenant)
Microsoft sign-in is restricted to the mhcis.com Azure AD tenant (AzureADMyOrg). The app registration “OpenInsure Auth” is a single-tenant registration managed in OpenTofu at infra/azure-auth/main.tf.
Tenant ID: ebd58a52-c818-4230-b150-348ae1e17975
Redirect URIs:
| Environment | URI |
|---|---|
| Production | https://auth-dev.openinsure.dev/api/auth/callback/microsoft |
| Local dev | http://localhost:8788/api/auth/callback/microsoft |
-
Redirect the user to the auth worker:
- Auth worker redirects to Azure AD for user authentication.
-
Azure AD redirects back to
/api/auth/callback/microsofton the auth worker. -
Auth worker issues a signed JWT and redirects to
<portal_url>/api/auth/callback?token=<JWT>. -
Each portal’s
/api/auth/callbackroute reads?token=JWT, sets the portal-specific cookie, and redirects to the dashboard.
user table) receive the org_admin role automatically on their first Microsoft SSO login. New users without a pre-existing account are rejected unless their email domain matches an allowlisted org.
:::
Session Management
| Endpoint | Method | Description |
|---|---|---|
/api/auth/get-session | GET | Returns { user, session, token } from the oi_session cookie |
/api/auth/sign-out | POST | Invalidates the current session and clears the cookie |
/api/auth/sign-up/email | POST | Creates a new user account with email + password |
JWT Format
All OpenInsure JWTs are HS256 signed with an 8-hour TTL.Portal JWT Cookies
Each portal verifies its own JWT cookie using a portal-specific secret:| Portal | Cookie name | Env secret | Worker |
|---|---|---|---|
| Admin (MHC IS ops) | oi_admin_token | JWT_SECRET | oi-sys-admin |
| Finance | oi_finance_token | FINANCE_JWT_SECRET | oi-sys-finance |
| Compliance | oi_compliance_token | COMPLIANCE_JWT_SECRET | oi-sys-compliance |
| Carrier portal | oi_carrier_token | CARRIER_JWT_SECRET | oi-sys-carrier |
| Producer portal | oi_producer_token | PORTAL_JWT_SECRET | oi-sys-producer |
| Underwriting Workbench | oi_uw_token | JWT_SECRET | oi-sys-uw |
| Policyholder portal | oi_session | POLICYHOLDER_JWT_SECRET | oi-sys-portal |
| Mobile app | SecureStore (Keychain) | POLICYHOLDER_JWT_SECRET | Direct API call |
/api/auth/callback route that:
- Reads the
?token=JWTquery parameter. - Verifies the JWT against
JWT_SECRET. - Sets the portal-specific cookie.
- Redirects to the dashboard.
Policyholder OTP Login (Mobile + Portal)
Policyholders authenticate via a 2-step OTP flow — no passwords are stored.- OTP is stored in KV with 10-minute TTL and cleared after use
- JWT includes
sub(policy ID),org,role,policyNumber,insuredName - The mobile app stores the JWT in SecureStore (Keychain) and adds biometric re-auth
- The web portal sets it as the
oi_sessioncookie
Using the React Auth Hook
useAuth() is provided by @openinsure/auth-hooks and works in all Next.js portals as well as apps/workbench (re-exported via context/AuthContext.tsx).
API Keys
API keys are the primary way to authenticate server-to-server requests. You can generate and manage API keys in the Admin Dashboard.Key Types
| Prefix | Environment | Usage |
|---|---|---|
oik_live_ | Production | Live production data |
oik_test_ | Sandbox | Testing and development |
Example Request
Roles & Permissions
OpenInsure uses Role-Based Access Control (RBAC) with the following common roles:| Role | Portal | Access |
|---|---|---|
superadmin | All | Full system access across all organizations |
org_admin | Admin / Carrier | Full access within their organization |
mhcis_operator | Admin (oi-sys-admin) | Platform vendor ops: orgs, programs, rate tables, rules, API keys, audit. Cross-org access via SpiceDB mhcis relation. |
finance_analyst | Finance (oi-sys-finance) | TigerBeetle ledger views, AR aging, statutory reports, reconciliation, report builder |
compliance_officer | Compliance (oi-sys-compliance) | Producer licensing, state filings, compliance analytics, audit log. No PHI values. |
underwriter | UW Workbench | Access to submissions, policies, and rating |
producer | Producer portal | Access to their own submissions and policies |
claims_adjuster | — | Access to assigned claims and related policy data |
claims_supervisor | — | Full PHI access for all org claims; approves settlements |
billing_admin | — | Access to invoices, payments, and financial reports |
auditor | — | Read-only access with mandatory audit logging |
policyholder | Policyholder portal | Portal-only JWT; read-only access to own policy and claims |
Relationship-Based Access Control (ReBAC)
For complex permission scenarios, OpenInsure integrates with SpiceDB (Zanzibar-style permissions). This allows fine-grained access control like:- “Can this Producer view a Claim linked to their Policy?”
- “Can this Underwriter approve a submission above their authority limit?”
Multi-Tenancy
Every API key and JWT is scoped to a specific Organization ID (org_id). The platform enforces strict isolation at the application layer using:
- JWT Claims — Every token contains an
orgclaim - App-Level Tenant Isolation — All queries are scoped by
orgIdin application middleware, ensuring queries only return rows matching the user’s organization - Application Middleware — All API routes validate org scope before processing
Environment Variables
Frontend applications should set the following environment variables:| Variable | Description |
|---|---|
VITE_AUTH_URL | Auth API base URL (e.g., https://auth-dev.openinsure.dev) |
NEXT_PUBLIC_AUTH_URL | For Next.js apps |
| Variable | Description |
|---|---|
AUTH_URL | Auth worker base URL (e.g., https://auth-dev.openinsure.dev) |
AUTH_SECRET | Auth worker signing secret — set via wrangler secret put |
JWT_SECRET | Admin + UW Workbench JWT signing key — set via wrangler secret put |
FINANCE_JWT_SECRET | Finance portal JWT signing key — wrangler secret put FINANCE_JWT_SECRET --name oi-sys-finance |
COMPLIANCE_JWT_SECRET | Compliance portal JWT signing key — wrangler secret put COMPLIANCE_JWT_SECRET --name oi-sys-compliance |
CARRIER_JWT_SECRET | Carrier portal JWT signing key |
POLICYHOLDER_JWT_SECRET | Policyholder portal JWT signing key |
PORTAL_JWT_SECRET | Producer portal JWT signing key |
API_SECRET | Machine/system bearer token — set via wrangler secret put |
JWT_SECRET or API_SECRET in wrangler.toml [vars] — those values are
committed to source control. Use wrangler secret put <NAME> to store them encrypted in the
Cloudflare dashboard, and use timingSafeEqual() (from @openinsure/auth) when comparing
tokens to prevent timing attacks.
:::