Authentication
Who can talk to the Manager, and how the Manager knows who they are.
The short version
- People sign in through the proxy, which runs OAuth 2.0 / OpenID Connect against Pocket ID. The Manager never sees a password or an OIDC token. The proxy forwards the signed-in identity in headers.
- Scripts and the CLI use a Personal Access Token (PAT). The proxy validates it with the Manager and then forwards the same identity headers.
- Backend services use a machine user: a Bearer token, or a username and password.
- The Manager accepts identity headers only from the proxy. Every request on a user-facing route must carry
X-Proxy-Authequal to the Manager'sproxy_secret(PFY_PROXY_SECRET). Without it the Manager answers401before it looks at anything else. In production the Manager refuses to serve the API without a secret.
The proxy-to-Manager trust boundary
The proxy and the Manager share a secret. The Manager compares it (constant-time) on every request to /query, /pilets/*, /api/audit/*, /api/access/*, /cli/authorize and all proxy-only routes (/api/validate-proxy-auth, /api/validate-oauth-user, /api/frontend-config/*, /pilet-feed/*, /pilet-bundle/*, /language-packs/*, /api/cli/login/*, and the scale-from-zero and comms routes).
Only after that check does the Manager read the identity headers the proxy injects:
X-Token-Subject- the user's id (a UUID)X-Token-User-EmailX-Token-User-NameX-User-Picture- optional avatar URL
The Manager creates or updates the user record from these on each request. A brand-new user has no access to anything until someone grants a project, tenant or application role.
Configure the secret on both sides. On the Manager:
# config.yml
proxy_secret: "<long random string>"or
export PFY_PROXY_SECRET="<long random string>"For local development without a proxy, dev_user_override.enabled: true in the development environment lets the Manager run without a secret and act as a fixed user. That is the only case in which an unset secret is tolerated.
Still keep the Manager off the public network
The shared secret stops header forgery, but the Manager's port should be reachable only from the proxy. Enforce that at the network layer as well. The machine-user routes (/api/machine/*, /esb/*) are not behind the shared secret; they authenticate the machine credential directly and are rate-limited.
Authentication methods
1. Browser login (OAuth 2.0 / OIDC through the proxy)
- You open the Manager URL. The proxy has no session for you and redirects you to Pocket ID.
- You sign in at Pocket ID. It redirects back to the proxy with an authorization code.
- The proxy exchanges the code for tokens and creates its session.
- The proxy checks the user with the Manager (
POST /api/validate-oauth-user) and, from then on, forwards your requests withX-Proxy-Authand the identity headers.
Successful and failed logins are written to the audit log. Repeated failures from one address or for one user are rate-limited (429).
sequenceDiagram
User->>Proxy: Open Manager UI
Proxy->>User: Redirect to Pocket ID
User->>Pocket ID: Sign in
Pocket ID->>User: Redirect with code
User->>Proxy: Callback with code
Proxy->>Pocket ID: Exchange code for tokens
Pocket ID->>Proxy: ID token + access token
Proxy->>Manager: POST /api/validate-oauth-user (X-Proxy-Auth)
Manager->>Proxy: valid, tenants
Proxy->>Manager: Request + X-Proxy-Auth + X-Token-* headers
Manager->>Proxy: Response
Proxy->>User: Page2. Personal Access Tokens
Authorization: Bearer pat_AbCd1234_9f8e7d6c5b4a...Format pat_<8-character prefix>_<random>. The proxy validates the token with the Manager (POST /api/validate-proxy-auth) and forwards the owning user's identity headers; the Manager then treats the request as that user. Tokens are hashed with PBKDF2-SHA256 (600,000 iterations, a salt per token). Expiry is optional, revocation is immediate, and lastUsedAt is tracked. Details: Personal Access Tokens.
sequenceDiagram
Client->>Proxy: Request + Bearer PAT
Proxy->>Manager: POST /api/validate-proxy-auth (X-Proxy-Auth)
Manager->>Proxy: valid, user
Proxy->>Manager: Request + X-Proxy-Auth + X-Token-* headers
Manager->>Proxy: Response
Proxy->>Client: Response3. Machine users
Authorization: Bearer <machine-user-token>Authorization: Basic <base64(username:password)>A machine user belongs to one environment of a project. The Manager's own machine routes (/api/machine/*, /esb/*) accept Bearer only. Through the proxy, both Bearer and Basic work, but only for applications where the machine user has an enabled endpoint assignment. Credentials use the same PBKDF2 parameters as PATs. There is no expiry; a maintenance sweep reports tokens older than cron.credential_rotation_max_age (default 90 days) and rotateMachineUserToken rotates one. Details: Machine Users.
sequenceDiagram
Service->>Manager: POST /api/machine/register-backend + Bearer token
Manager->>Manager: Verify token, load environment
Manager->>Service: Response4. CLI device login
pfy login without a token starts a device-authorization flow: POST /api/cli/login/start and POST /api/cli/login/poll (proxy secret only, no user credential) and a consent page at /cli/authorize behind the normal browser login. Approving mints a 90-day PAT and delivers it to the CLI encrypted, exactly once. See Browser login for the CLI.
Pocket ID configuration
The Manager talks to Pocket ID with an API key for backend operations.
pocket_id:
host: http://pocketid:1411
api_key: your-api-key-hereor
export PFY_POCKET_ID_HOST=https://pocketid.example.com
export PFY_POCKET_ID_API_KEY=your-api-key-herepocket_id.api_key is required in production; the Manager refuses to start without it. The default host is http://pocketid.localhost.
Setup: deploy Pocket ID (Deployment guide), create an API key in its admin UI, put it in the Manager configuration, and restart the Manager. The OIDC client the proxy uses for user login is configured on the proxy: Proxy configuration.
Sessions
Sessions belong to the proxy and Pocket ID. The Manager is stateless and re-reads the identity headers on every request. Session length, refresh and logout are configured on the proxy and in Pocket ID, not in the Manager. A user may have several sessions at once.
Authorization after authentication
Once the Manager knows who you are, roles decide what you may do:
- System role, per user:
normal,auditoradministrator. - Project role, per user and project:
readonly,tester,developer,maintainerorowner. - Tenant roles, configurable per project over a fixed permission vocabulary. The platform seeds
member,adminandowner. Checks test permissions, not role names.
The full model is in the Access Model. Day-to-day management is in Users.
Forwarding the tenant role to an application
Per (tenant, application), the forward_role setting (default off) makes the proxy inject:
X-User-Tenant-Role- the role name in the selected tenant (configurable per project, so not a stable contract)X-User-Tenant-Permissions- comma-separated permissions from the fixed vocabulary; gate on theseX-User-Picture- the Pocket IDpictureclaim when present
The same values appear in window.__PRODUCTIFY__ as tenantRole, tenantPermissions and user.picture. The proxy strips client-supplied copies of these headers. The platform forwards the membership fact; the application decides what it means.
Troubleshooting
| Response | Meaning and fix |
|---|---|
401 "manager misconfiguration: no proxy secret is configured (PFY_PROXY_SECRET is unset and dev_user_override is not enabled)" | Set proxy_secret on the Manager and the matching secret on the proxy. |
401 "missing or invalid X-Proxy-Auth header: this endpoint is reachable only through the trusted proxy" | The request did not come through the proxy, or the two secrets differ. |
401 "no authenticated user: the request reached the manager without the X-Token-Subject / X-Token-User-Email / X-Token-User-Name headers" | You are not signed in, or the proxy is not forwarding identity. Sign in through the proxied URL. |
401 with error code invalid_token or token_expired | Bad, revoked or expired PAT. |
401 "Invalid credentials" on /api/machine/* | Wrong, rotated or disabled machine-user token. Basic authentication is not accepted on these routes. |
429 | Too many failed attempts; wait for Retry-After. |
| Login loop | Wrong issuer or redirect URL in the proxy's OIDC client, or clock skew between the proxy and Pocket ID. |
Good practice
- Serve everything over HTTPS.
- Keep the Manager reachable only from the proxy.
- Give PATs an expiry when the job is short-lived, and revoke what you no longer use.
- Use one machine user per service and grant it only the endpoints it needs.
- Keep secrets in environment variables or a secret manager, never in a repository.
- Turn on multi-factor authentication in Pocket ID.