Authentication
How ProxifAI authenticates users and machines — JWT for the API, OIDC for browser login, PKCE for the CLI, and HMAC-signed pfai_ tokens for workflow executions.
ProxifAI’s authentication is intentionally simple in OSS: an HS256 JWT signed by JWT_SECRET is the source of truth for API access, with optional Keycloak OIDC bolted on top for browser login and SSO. The CLI uses PKCE OAuth, and workflow runtimes get short-lived HMAC-signed pfai_<execID>_<sig> tokens that double as API credentials with the executing workflow’s identity.
How it works
Three auth surfaces, all funneling into the same JWT validator:
┌── Bearer <jwt>
Browser ──► OIDC (Keycloak) ──┐ │
├──► /api/v1/* ──► auth.Middleware ──► JWT verified (HS256)
CLI ──► PKCE OAuth ───────┤ │
│ │
Workflow ──► pfai_ token ──────┘ └── Authorization: Bearer pfai_<execID>_<sig>
│
└── HMAC verified by gateway middleware
All paths converge on internal/auth/Middleware (auth.go), which:
- Reads
Authorization: Bearer <token> - Parses and validates with
jwt.ParseWithClaimsusingJWTSecretand HMAC - Injects
userId,email,orgIdand the fullClaimsstruct into the request context
If no token is present, requests pass through — handlers decide whether auth is required.
JWT claims
Issued by ProxifAI on login (after OIDC exchange or first-party password auth). Format defined in auth.Claims:
| Claim | Source | Notes |
|---|---|---|
sub | User ID | The canonical user identifier |
email | User email | Case-normalized |
name | User display name | Optional |
org_id | Active org | Set when the user is scoped to an org |
iat, exp, iss, aud | Standard | exp controls token lifetime |
Tokens are HS256-signed with JWT_SECRET. There’s no key rotation in OSS — rotating the secret invalidates every active token (logout-everyone). Set the secret to a long random value at deployment time and store it in your secret manager.
OIDC (browser login)
Routes mounted under /api/v1/oidc/* (oidc_auth.go):
| Endpoint | Use |
|---|---|
GET /api/v1/oidc/authorize?rd=... | Redirects to the configured IdP (Keycloak by default) |
GET /api/v1/oidc/callback | IdP returns the user here; ProxifAI exchanges the code, mints a JWT, sets a cookie |
GET /api/v1/oidc/logout | Clears the cookie and bounces through the IdP’s logout |
OIDC_* env vars wire up the provider. Keycloak is the default but any standards-compliant OIDC provider works (Auth0, Okta, Google Workspace, Azure AD).
# Required
OIDC_ISSUER=https://id.example.com/realms/proxifai
OIDC_CLIENT_ID=proxifai
OIDC_CLIENT_SECRET=…
# Optional
OIDC_SCOPES="openid profile email" # default
OIDC_REDIRECT_URL=https://your-host/api/v1/oidc/callback # auto-derived from BASE_URL if unset
OIDC is opt-in. If OIDC_ISSUER is unset, the routes return 404 and the platform falls back to first-party email/password (admin user seeded from ADMIN_EMAIL / ADMIN_PASSWORD).
The local k3d setup ships a Keycloak realm at http://id.proxifai.com with [email protected] / admin and [email protected] / user pre-seeded for development.
CLI authentication (PKCE)
pfai auth login runs a standards-compliant PKCE OAuth flow (internal/pfai/cmd/auth.go):
GET /api/v1/auth/cli-configreturns the PKCE-shaped config (authorizationEndpoint,tokenEndpoint,clientId)- The CLI generates a code verifier + challenge, opens the user’s browser to the authorize URL with the challenge attached
- After consent, the IdP redirects to
http://127.0.0.1:<random>/callbackwith an authorization code - The CLI exchanges the code + verifier for an access token, then calls ProxifAI to exchange the access token for a long-lived
pfaitoken - The result is written to
~/.proxifai/credentials.json(and mirrored into~/.proxifai/contexts.yaml)
For non-interactive use:
| Approach | When |
|---|---|
PFAI_TOKEN + PFAI_SERVER env | CI pipelines, scripts |
pfai context add ci --server X --token Y | Multiple servers/identities to switch between |
pfai auth login --as <name> once, then export the token | One-time setup, ship the token as a secret |
See CLI for the full surface.
Workflow tokens (pfai_<execID>_<sig>)
Workflow executions need API credentials too — pfai exec status, agent containers calling the LLM gateway, etc. These get an HMAC-signed token shaped:
pfai_<execID>_<sig>
Where:
execIDis the workflow execution IDsigishex(HMAC-SHA256(execID, JWT_SECRET))[:16]
The verifier in internal/llmgateway/middleware/auth.go (and parallel checks in core handlers) recomputes the MAC and constant-time compares. Tokens carry the executing workflow’s identity, so:
- LLM gateway calls attribute usage to the right workflow
- Credit checks (Enterprise) deduct against the right org
- API calls inherit the workflow’s
runtime_config.permissionsscope
The LLM_GATEWAY_HMAC_SECRET env var overrides JWT_SECRET for the gateway-only path; default is to use JWT_SECRET.
Personal access tokens (PATs)
For human-controlled scripts and integrations, create a PAT under Settings → Personal Access Tokens. Token format is pfai_ + 40 hex chars; the full value is shown once at creation, stored as SHA-256 hash thereafter.
PAT scopes (from TokenScope in models.go):
| Scope | Implies |
|---|---|
read | All *:read scopes |
write | read + all *:write scopes |
repo:read · repo:write | Git protocol + forge API |
issues:read · issues:write | Plan API |
pr:read · pr:write | Pull-request API |
admin:read · admin:write | Admin endpoints |
pipeline | Pipeline dispatch and management |
PATs use the same Authorization header as JWTs:
curl http://localhost:3000/api/v1/issues \
-H "Authorization: Bearer pfai_a1b2c3..."
Manage PATs from pfai token or via:
| Method · Path | Purpose |
|---|---|
GET /api/v1/tokens | List your PATs |
POST /api/v1/tokens | Create — value returned once in response |
PATCH /api/v1/tokens/{id} | Rename, change scopes |
DELETE /api/v1/tokens/{id} | Revoke immediately |
Environment variables
| Variable | Required | Effect |
|---|---|---|
JWT_SECRET | yes (auto-generated on first boot if unset) | HMAC secret for HS256 token signing/verifying |
LLM_GATEWAY_HMAC_SECRET | no | Override JWT_SECRET for pfai_<execID> token verification only |
BASE_URL | derived | Used to build redirect URIs and link generation; defaults to http://localhost:<PORT> |
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET | OIDC only | Enable OIDC browser login |
OIDC_SCOPES | optional | Default openid profile email |
OIDC_REDIRECT_URL | optional | Auto-derived from BASE_URL if unset |
ADMIN_EMAIL, ADMIN_PASSWORD | optional | Seeded admin when OIDC is disabled — defaults admin / admin |
Multi-factor authentication
MFA isn’t built into the OSS first-party auth. To enforce MFA for browser login, configure it on your OIDC provider (Keycloak, Okta, etc.) — ProxifAI honors whatever the IdP returns and the IdP can challenge for MFA before issuing the access token. There’s no MFA layer on PATs or pfai_ workflow tokens; protect those by scoping permissions narrowly and rotating regularly.
See also
35 permissions, 3 default roles, custom roles, per-user overrides, project-level roles.
Inviting members, assigning roles, team membership.
The CLI auth flow end-to-end — login, contexts, env vars.
How the gateway accepts both PATs and pfai_ workflow tokens.