Skip to main content

Authentication

Honeyframe authenticates API requests with bearer tokens. Two kinds of token are accepted on the Authorization: Bearer <token> header:

  • JWTs — short-lived (default 60 minutes), minted by logging in with username/password (or via Google). Suited to interactive sessions; re-login when the token expires.
  • Personal Access Tokens (PATs) — long-lived hf_… credentials you mint once and use from scripts, CI, and the SDK. The right choice for unattended automation. See Personal Access Tokens below.

There is no request signing and no OAuth2-with-refresh-tokens. The auth middleware branches on the token: an hf_-prefixed value is resolved as a PAT, anything else is decoded as a JWT.

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.

Personal Access Tokens

Personal Access Tokens (PATs) are long-lived hf_… credentials tied to a user, intended for the SDK, the honeyframe CLI, and any script that needs to authenticate without a password or the short-lived JWT. A PAT resolves the owning user, so it can never exceed that user's permissions — but it can be granted narrower: pass a list of capability scopes at mint time and the token is limited to them (a token strictly ≤ the user, in the Dataiku project-key style).

Mint, list, and revoke tokens from the platform UI (avatar menu → Personal Access Tokens) or via the API:

# Mint a token — the secret is shown ONCE; store it immediately.
curl -X POST https://platform.your-domain.com/api/auth/tokens \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"ci-pipeline"}'
{ "id": 3, "name": "ci-pipeline", "token": "hf_xxxxxxxxxxxxxxxxxxxx", "created_at": "..." }
# List your tokens (metadata only — the secret is never returned again).
curl -H "Authorization: Bearer $TOKEN" https://platform.your-domain.com/api/auth/tokens

# Revoke one (scoped to the owner).
curl -X DELETE https://platform.your-domain.com/api/auth/tokens/3 \
-H "Authorization: Bearer $TOKEN"

Properties:

  • The raw hf_… secret is displayed once at creation and stored only as a keyed HMAC-SHA256 hash at rest (upgraded from bare SHA-256; existing tokens upgrade transparently on first use). There is no way to retrieve it later — mint a new one if you lose it.
  • Use it exactly like a JWT: Authorization: Bearer hf_…. The middleware resolves the owning user, rejecting unknown, revoked, or expired tokens, and stamps last_used_at.
  • Default 90-day expiry. New tokens expire in 90 days unless you set an explicit lifetime; pass 0 to mint a never-expiring token.
  • Scopes. Set scopes at mint time to restrict a token to a subset of capabilities (see the intro above). Omit for a full-permission token (still ≤ the user).
  • Limits. Each user is capped at PAT_MAX_PER_USER tokens (default 20). Per-token rate limiting is available via PAT_RATE_LIMIT_PER_MIN (default 0 = off).
  • Token mint and revoke are written to the audit log.

Org-admin token governance

An org admin can inventory and revoke tokens across the organization (not just their own), so a departing user's or a leaked automation token can be cut off centrally:

# List every PAT in the org (org.admin)
curl -H "Authorization: Bearer $TOKEN" https://platform.your-domain.com/api/auth/tokens/admin

# Revoke any token by id (org.admin)
curl -X DELETE https://platform.your-domain.com/api/auth/tokens/admin/3 \
-H "Authorization: Bearer $TOKEN"

The SDK and CLI manage PATs directly — client.create_pat / list_pats / revoke_pat and honeyframe pat create|list|revoke. See SDK → Authentication.

PATs replace the older "create a dedicated user and cache its JWT" service-account workaround. If you still need a shared service identity, create a regular user with the narrowest sufficient role and mint a PAT for it.

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.