Authentication
All API requests to OpenInsure must be authenticated using a Bearer token in the Authorization header.
Authentication Overview
Section titled “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
The auth service is deployed at https://auth-dev.openinsure.dev.
Sign-In Methods
Section titled “Sign-In Methods”Email / Password
Section titled “Email / Password”POST https://auth-dev.openinsure.dev/api/auth/sign-in/emailContent-Type: application/json
{ "email": "user@example.com", "password": "••••••••"}Response:
{ "user": { "id": "usr_…", "email": "user@example.com", "role": "org_admin" }, "session": { "id": "ses_…", "expiresAt": "2026-03-08T14:00:00Z" }, "token": "eyJhbGciOiJIUzI1NiJ9…"}The response also sets an oi_session cookie that portal middleware reads automatically.
Microsoft SSO (mhcis.com Tenant)
Section titled “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 |
Flow:
-
Redirect the user to the auth worker:
GET https://auth-dev.openinsure.dev/api/auth/sign-in/microsoft?callbackURL=<portal_url>/api/auth/callback -
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.
Session Management
Section titled “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
Section titled “JWT Format”All OpenInsure JWTs are HS256 signed with an 8-hour TTL.
{ "sub": "usr_01J8K3M4N5P6Q7R8S9T0", "org": "org_01HW3K2T9Z6D4V8X5N1M", "role": "org_admin", "iat": 1741430400, "exp": 1741459200}Portal JWT Cookies
Section titled “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 |
Every portal has an /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)
Section titled “Policyholder OTP Login (Mobile + Portal)”Policyholders authenticate via a 2-step OTP flow — no passwords are stored.
# Step 1: Request OTP (sends 6-digit code to email on file)POST /auth/policyholder-otp-requestContent-Type: application/json
{ "policyNumber": "MHC-COM-2026-04821" }# Step 2: Verify OTP and receive JWTPOST /auth/policyholder-tokenContent-Type: application/json
{ "policyNumber": "MHC-COM-2026-04821", "otp": "123456" }Response:
{ "token": "eyJhbGciOiJIUzI1NiJ9…", "role": "policyholder", "orgId": "org_uuid", "sub": "policy-id", "policyNumber": "MHC-COM-2026-04821", "insuredName": "Midwest Freight Corp"}- 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
Section titled “Using the React Auth Hook”import { AuthProvider, useAuth } from '@openinsure/auth-hooks';
function App() { return ( <AuthProvider baseURL="/api/auth"> <Dashboard /> </AuthProvider> );}
function Dashboard() { const { user, token, loading, login, logout } = useAuth();
if (loading) return <div>Loading...</div>; if (!user) return <div>Please log in</div>;
return ( <div> <p>Welcome, {user.email}</p> <button onClick={logout}>Logout</button> </div> );}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
Section titled “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
Section titled “Key Types”| Prefix | Environment | Usage |
|---|---|---|
oik_live_ | Production | Live production data |
oik_test_ | Sandbox | Testing and development |
Example Request
Section titled “Example Request”curl -X GET https://api.openinsure.dev/v1/policies \ -H "Authorization: Bearer oik_live_abc123..."Roles & Permissions
Section titled “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)
Section titled “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
Section titled “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
// Example: orgId scoping applied by middleware before every queryconst policies = await db.select().from(policiesTable).where(eq(policiesTable.orgId, ctx.orgId));Environment Variables
Section titled “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 |
Backend/API environment variables:
| 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 |