Lewati ke konten utama

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 inherits the user's permissions; it is not separately scoped.

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 SHA-256 hash at rest. 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.
  • Token mint and revoke are written to the audit log.

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.