Authentication
NimbleBrain uses pluggable authentication adapters configured via instance.json in the work directory. The adapter determines how users identify themselves — from simple API keys to full enterprise SSO.
Dev mode (no auth)
Section titled “Dev mode (no auth)”When no instance.json exists, NimbleBrain runs without authentication. All requests receive a default developer identity and a default workspace is created automatically. This is intended for local development only.
Auth adapters
Section titled “Auth adapters”Configure authentication by setting the adapter field in instance.json:
| Adapter | Best for | Identity providers |
|---|---|---|
local | Self-hosted, small teams | API keys + session cookies |
oidc | BYO identity provider | Any OIDC-compliant provider (Auth0, Keycloak, Okta, Google, Entra, etc.) |
workos | Managed enterprise SSO + directory sync | Google Workspace, Microsoft Entra, Okta, OneLogin, and more via WorkOS |
Per-user API keys stored locally with bcrypt hashing. Users authenticate via Bearer token or session cookie.
{ "auth": { "adapter": "local" }}Generic OIDC adapter that works with any provider supporting OpenID Connect Discovery. NimbleBrain verifies JWTs against the provider’s JWKS endpoint and restricts access to allowed email domains.
{ "auth": { "adapter": "oidc", "issuer": "https://your-provider.example.com", "clientId": "your-client-id", "allowedDomains": ["yourcompany.com"] }}| Field | Required | Description |
|---|---|---|
issuer | Yes | OIDC issuer URL. Must serve a /.well-known/openid-configuration endpoint. |
clientId | Yes | OAuth client ID from your provider. |
allowedDomains | Yes | Email domains permitted to authenticate (e.g., ["yourcompany.com"]). |
jwksUri | No | JWKS URI override. If omitted, discovered automatically via the issuer’s .well-known metadata. |
Use this adapter when you already have an OIDC-compliant identity provider and want direct integration without a middleman.
WorkOS
Section titled “WorkOS”Managed enterprise authentication via WorkOS. This adapter adds capabilities beyond raw OIDC:
- Enterprise SSO — Google Workspace (SAML), Microsoft Entra, Okta, OneLogin, PingFederate, and any generic SAML/OIDC provider
- Directory Sync — Automatic user provisioning and deprovisioning from your identity provider
- Admin Portal — Self-serve SSO and directory configuration for your end users
- Organization scoping — Restrict access to members of a specific WorkOS organization
{ "auth": { "adapter": "workos", "clientId": "client_...", "redirectUri": "https://app.example.com/v1/auth/callback" }}| Field | Required | Description |
|---|---|---|
clientId | Yes | WorkOS Client ID (client_...). |
redirectUri | Yes | OAuth callback URL (must match your WorkOS environment settings). |
organizationId | No | WorkOS Organization ID — scopes authentication to a specific organization. |
apiKey | No | WorkOS API key. Can also be set via the WORKOS_API_KEY environment variable. |
On first login, NimbleBrain verifies the user’s organization membership, syncs their profile, and provisions a private workspace automatically.
SSO/OIDC endpoints
Section titled “SSO/OIDC endpoints”These endpoints are active when using the oidc or workos adapter:
GET /v1/auth/provider— returns the auth adapter type and configurationGET /v1/auth/authorize— redirects to the provider’s authorization pageGET /v1/auth/callback— handles the OAuth callback and sets a session cookiePOST /v1/auth/refresh— refreshes the session token
Bearer Token
Section titled “Bearer Token”Include your API key in the Authorization header:
curl http://localhost:27247/v1/bootstrap \ -H "Authorization: Bearer your-api-key"The server performs a constant-time comparison to prevent timing attacks. If the key does not match, the server returns 401 with an empty body.
Session Cookie
Section titled “Session Cookie”For browser-based clients, exchange the API key for an HttpOnly cookie using the login endpoint. The cookie is automatically sent on subsequent requests.
POST /v1/auth/login
Section titled “POST /v1/auth/login”Validate the API key and receive a session cookie.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The API key to validate |
Response:
{ "ok": true}On success, the response includes a Set-Cookie header:
Set-Cookie: nb_session=<key>; HttpOnly; SameSite=Strict; Path=/; Max-Age=604800Cookie properties:
- HttpOnly — not accessible from JavaScript
- SameSite=Strict — only sent on same-site requests
- Secure — set when the server is not running on localhost
- Max-Age=604800 — 7-day expiry
Error responses:
| Status | Condition |
|---|---|
| 400 | Missing or non-string key field |
| 401 | Key does not match NB_API_KEY |
Example:
curl -X POST http://localhost:27247/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"key": "your-secret-key"}' \ -c cookies.txt{ "ok": true}POST /v1/auth/logout
Section titled “POST /v1/auth/logout”Clear the session cookie. Requires authentication.
Response:
{ "ok": true}The response sets the cookie’s Max-Age to 0, which instructs the browser to delete it.
Example:
curl -X POST http://localhost:27247/v1/auth/logout \ -H "Authorization: Bearer your-secret-key"{ "ok": true}GET /v1/auth/session
Section titled “GET /v1/auth/session”Check whether the current session is valid. Requires authentication. Returns 200 if authenticated, 401 otherwise.
Response:
{ "authenticated": true}Example:
curl http://localhost:27247/v1/auth/session \ -b cookies.txt{ "authenticated": true}Auth Precedence
Section titled “Auth Precedence”When both a Bearer token and a session cookie are present, the Bearer token takes precedence. The server checks the Authorization header first, then falls back to the nb_session cookie.
API Key Requirements (local adapter)
Section titled “API Key Requirements (local adapter)”- Minimum length: 8 characters
- Keys shorter than 16 characters produce a startup warning
- The server refuses to start if the key is shorter than 8 characters
CORS Configuration
Section titled “CORS Configuration”CORS behavior depends on the auth configuration and ALLOWED_ORIGINS:
| Auth mode | ALLOWED_ORIGINS | Behavior |
|---|---|---|
| Dev (no auth) | N/A | Access-Control-Allow-Origin: * |
| Auth configured | Set (comma-separated) | Only matching origins get CORS headers with credentials: true |
| Auth configured | Not set | Same-origin only (no CORS headers) |
Set ALLOWED_ORIGINS as a comma-separated list of origins:
export ALLOWED_ORIGINS="https://app.example.com,https://staging.example.com"The server always responds to OPTIONS preflight requests with:
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONSAccess-Control-Allow-Headers: Content-Type, Authorization, Mcp-Session-Id, Last-Event-ID, Mcp-Protocol-Version, X-Workspace-IdAccess-Control-Expose-Headers: Mcp-Session-Id, Mcp-Protocol-VersionFailed Auth Logging
Section titled “Failed Auth Logging”Every failed authentication attempt is logged to stderr with the client IP and timestamp:
[nimblebrain] AUTH FAIL ip=203.0.113.42 timestamp=2025-10-15T14:30:00.000Z