Skip to main content

Authentication

Orunbase identity is built on two objects: a user (usr_…) — a globally unique, email-anchored account that can belong to many workspaces — and a session (sps_ses_<id>.<secret>) — a revocable bearer token minted at sign-in. Every authenticated console and API request presents the session token as Authorization: Bearer <token>; the edge resolves it to an actor before any workspace-scoped routing happens.

Sessions live for 30 days and are stored hashed server-side — the raw token is returned exactly once, at login. On your first sign-in a workspace is created for you automatically, so you land in a working scope rather than an empty account.

Sign in with an email code

Login is a two-step, magic-link-style challenge: request a code, then redeem it. The code is 6 digits, delivered to your email (notification category security), and the challenge expires after 10 minutes.

Step 1 — start the challenge:

curl -X POST https://api.orunbase.com/v1/auth/login/start \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com"}'
{
"data": {
"challengeId": "chl_9f8e7d6c5b4a39281706f5e4",
"expiresAt": "2026-07-02T12:10:00.000Z",
"delivery": { "mode": "email", "emailHint": "d***@example.com" }
},
"meta": { "requestId": "req_0a1b2c3d4e5f", "cursor": null }
}

Step 2 — redeem the code:

curl -X POST https://api.orunbase.com/v1/auth/login/complete \
-H "Content-Type: application/json" \
-d '{"challengeId": "chl_9f8e7d6c5b4a39281706f5e4", "code": "123456"}'
{
"data": {
"token": "sps_ses_1f2e3d4c5b6a79880917263544332211.k3J9…",
"tokenType": "bearer",
"expiresAt": "2026-08-01T12:00:00.000Z",
"user": { "id": "usr_7a6b5c4d3e2f10099887766554433221", "email": "dev@example.com", "displayName": null }
},
"meta": { "requestId": "req_1b2c3d4e5f6a", "cursor": null }
}

An unknown or wrong code returns not_found; an expired or already-used challenge returns precondition_failed. Challenges are single-use.

With the SDK:

import { OrunCloud } from "@saas/sdk";

const client = new OrunCloud({ baseUrl: "https://api.orunbase.com", auth: { kind: "bearer", token: "" } });

const start = await client.auth.loginStart({ email: "dev@example.com" });
// … user reads the 6-digit code from their inbox …
const { token, user } = await client.auth.loginComplete({
challengeId: start.challengeId,
code: "123456",
});
note

Login endpoints sit in the auth rate-limit family — 10 requests per identity and 60 per workspace per 60 s — the strictest bucket on the platform. Failed and successful attempts both record security events.

Sign in with OAuth

The console can also sign users in through an OAuth provider. The flow is browser-redirect, not JSON:

MethodPathDescription
GET/v1/auth/oauth/providersProviders that are fully configured — the console renders a button per entry
GET/v1/auth/oauth/{provider}/start302 to the provider's consent page
GET/v1/auth/oauth/{provider}/callbackProvider redirect target; issues the same session token

A successful OAuth login mints the same sps_ses_… session as the email flow. Account linking is verified-email-or-bust: a provider identity with no verified email cannot attach to or create an account; a verified email that matches an existing user links to that user.

note

GitHub (and Google) OAuth sign-in is scaffolded end to end but credential-blocked in production — the provider only appears in /v1/auth/oauth/providers once OAuth app credentials are configured (see specs/epics/saas-baseline, item B1). Email-code login is always available.

Inspect the current session

curl https://api.orunbase.com/v1/auth/session \
-H "Authorization: Bearer $TOKEN"
{
"data": {
"session": { "id": "ses_1f2e3d4c5b6a79880917263544332211", "expiresAt": "2026-08-01T12:00:00.000Z", "createdAt": "2026-07-02T12:00:00.000Z" },
"user": { "id": "usr_7a6b5c4d3e2f10099887766554433221", "email": "dev@example.com", "displayName": "Rahul", "lastOrgSlug": "acme" }
},
"meta": { "requestId": "req_2c3d4e5f6a7b", "cursor": null }
}

An expired or revoked token returns 401 unauthenticated. The SDK equivalent is client.auth.getSession().

Log out

POST /v1/auth/logout revokes the session server-side and evicts it from the edge's actor cache, so the token stops working immediately — not at cache expiry.

await client.auth.logout(); // → { success: true }

Manage your profile

GET /v1/auth/profile returns the signed-in user; PATCH /v1/auth/profile is a partial update of displayName and lastOrgSlug (the cross-device default-landing hint the console maintains). Profile routes are session-token only — an API key gets 403.

curl -X PATCH https://api.orunbase.com/v1/auth/profile \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName": "Rahul Varghese"}'
const { user } = await client.auth.updateProfile({ displayName: "Rahul Varghese" });

Changing displayName records a security event; updating lastOrgSlug does not (it changes on routine navigation).

Review security events

GET /v1/auth/security-events lists the identity-level audit trail for your own account — login challenges, session creation and revocation, profile changes, API key lifecycle. Cursor-paginated (limit + cursor).

curl "https://api.orunbase.com/v1/auth/security-events?limit=20" \
-H "Authorization: Bearer $TOKEN"

Each event carries eventType (e.g. login.challenge.created, session.created, session.revoked, user.profile.updated, api_key.created), outcome, occurredAt, requestId, ip, userAgent, and metadata. Secret-bearing metadata (codes, token hashes, key material) is redacted before it leaves the service.