Skip to main content

Authentication

Honeyframe authenticates API requests with JWT bearer tokens. There is no API key flow, no request signing, and no OAuth2-with-refresh-tokens — clients log in with a username/password (or via Google) to mint a token, send it as Authorization: Bearer <token> on subsequent requests, and re-login when the token expires.

Token format

Tokens are signed JWTs (HS256 by default, configurable via JWT_ALGORITHM) with the org's JWT_SECRET. The payload is small:

{
"user_id": 42,
"role": "editor",
"exp": 1716200000
}
  • user_id — primary key of the user row (hubstudio.users.user_id).
  • role — the legacy flat role string (admin, editor, viewer, management, cs_staff). Group memberships are not in the token; they're looked up server-side per request.
  • exp — Unix timestamp of expiry. Default lifetime is JWT_EXPIRE_MINUTES (60). Re-login to get a fresh token.

Decoding logic lives in paas/backend/services/auth_service.py:decode_access_token. The middleware reads the token, decodes it, looks up the user row, and attaches user to the request.

Logging in

TOKEN=$(curl -s -X POST https://platform.your-domain.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"you@example.com","password":"<redacted>"}' \
| jq -r '.access_token')

Successful response:

{
"access_token": "eyJhbGciOi...",
"token_type": "bearer",
"must_reset_password": false
}

must_reset_password is true for users that were just created or whose password was just reset by an admin. Clients should redirect to a password-change flow before allowing further actions.

Authenticating a request

curl -H "Authorization: Bearer $TOKEN" https://platform.your-domain.com/api/me

The platform returns 401 for missing/invalid tokens, 403 when the token is valid but the user lacks the required role or permission. See Permissions Reference for the authorization layer.

Google sign-in

POST /api/auth/google exchanges a Google ID token for a Honeyframe access token:

curl -X POST https://platform.your-domain.com/api/auth/google \
-H 'Content-Type: application/json' \
-d '{"id_token":"<google-id-token>"}'

The platform verifies the ID token's signature against Google's published JWKs, looks up the user by email, and issues a Honeyframe JWT. Configure the trusted Google client ID and email-domain allowlist in the Platform .env:

GOOGLE_CLIENT_ID=...apps.googleusercontent.com
GOOGLE_ALLOWED_DOMAINS=your-company.com,subsidiary.com

Password change

Authenticated users change their own password:

curl -X POST https://platform.your-domain.com/api/auth/change-password \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"old_password":"...","new_password":"..."}'

Admins can reset another user's password through POST /api/users/{user_id}/reset-password. The endpoint emails a temporary password to the user and sets must_reset_password=true.

Forgotten password

curl -X POST https://platform.your-domain.com/api/auth/forgot-password \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com"}'

Generates a one-time reset token, emails it to the user, and stores the token hash on the user row. The user follows the email link, posts to /api/auth/reset-password with {token, new_password}, and receives a fresh JWT.

Service accounts

There is no first-class service-account API. The conventional pattern is:

  1. Create a regular user (POST /api/users) with a strong random password and role='admin' (or narrower).
  2. Log in with that user's credentials from your service.
  3. Cache the JWT and re-login on 401.

When a first-party service-account API ships, it will issue long-lived tokens scoped by org_id and a permission set. Until then, treat the regular user pattern as the supported path.

Token rotation

JWT secrets are loaded from JWT_SECRET in the Platform .env. To rotate:

  1. Update JWT_SECRET to a new random value.
  2. Restart hub-platform.
  3. All existing tokens become invalid; clients must log in again.

There is no key-rolling mechanism that accepts both the old and new secret simultaneously. Schedule rotation during a known-quiet window or a planned re-auth event.

For the license signing key (a separate signing key used for licensed installs, not for user tokens), see your operator runbook — license keys rotate via a different path that does not invalidate user JWTs.

Multi-tenant context

Authenticated requests against multi-tenant endpoints require X-Org-Id:

curl -X POST https://platform.your-domain.com/api/datasets \
-H "Authorization: Bearer $TOKEN" \
-H "X-Org-Id: 42" \
-d '...'

Without X-Org-Id, the platform falls back to the user's default organization (users.default_org_id). If the user belongs to multiple orgs and you don't set the header explicitly, you're at the mercy of the default — set it explicitly for any non-trivial integration.

CORS

The Platform API only accepts requests from origins listed in CORS_ALLOWED_ORIGINS (Platform .env). The default in a single-host deployment is the three Honeyframe domains. Add your app's origin if you're integrating from elsewhere.

Rate limits

See Reverse Proxy for the auth-bucket rate limits (5 req/s on /api/auth/*). Exceeding them returns 503 from nginx; the request never reaches the FastAPI handler.