Skip to content

Sign-in Security

Philter has no built-in user interface. A user interface that runs separately signs people in through Philter, and Philter UI, in development, is being built to work this way: it sends a person's username and password to Philter and receives a session key for that person's Philter user. Philter stays the only user store, the user's role and scopes decide what the person may do, and the audit log names the person rather than a key the interface shares.

This page describes how sign-in is protected. For the endpoints, see the Sign-in API, the Users API, and the API Keys API.

Enabling password sign-in

Password sign-in is disabled by default, so a deployment that runs no user interface exposes no login endpoint. Enable it with PASSWORD_SIGN_IN_ENABLED=true. While it is disabled, POST /api/sign-in and POST /api/sign-in/mfa answer 404 Not Found, whatever the request contains. It is an environment variable rather than an admin setting, so an administrator's API key cannot turn it on.

Automation does not sign in. It uses long-lived API keys, and users created for it need no password.

Passwords

  • Rules. At least 16 characters and at most 72 bytes in UTF-8, the most bcrypt reads. Stored as bcrypt hashes and never returned or audited.
  • Optional. A user without a password cannot sign in. Every kind of failed sign-in (wrong password, unknown username, a user without a password, a deactivated user) gets the same 401 in about the same time, so sign-in does not reveal which usernames exist.
  • The first administrator. The admin user starts without a password. Set it with the bootstrap API key through PUT /api/users/admin/password. There is no bootstrap password.
  • Set by an administrator. A password given when an administrator creates a user, or set or reset later by an administrator on another user, must be changed at that user's next sign-in, because someone else chose it. An administrator setting their own first password, as with the bootstrap key, is not asked to change it.
  • Changed by the user. PUT /api/users/me/password requires the current password, and the new one must differ. An administrator's key cannot replace its own user's password without the current one, so a stolen key cannot take over its own account.

First sign-in and resets

A user who must change their password, or must enroll in MFA, gets a session key that can only do that, read the limits, and sign out; any other request is refused with 403 Forbidden. The sign-in response says which with passwordChangeRequired and mfaEnrollmentRequired. Changing the password, or confirming MFA enrollment, revokes the key, and the person signs in again.

An administrator can reset any user's password and remove their MFA, so an administrator can sign in as any user. ADMIN_CROSS_USER_ACCESS_ENABLED does not prevent this: it limits reaching other users' resources with owner, not account administration. A reset revokes the user's session keys and their old password stops working, so they notice, and it is recorded as user_password_reset, with user_mfa_removed for the MFA. Review those events in the audit log.

Session keys

A session key is the key issued at sign-in.

  • Scopes. It holds every scope, with the user's role still deciding administrator access. It cannot create API keys, and can narrow another key's scopes but not widen them, so a session cannot produce a long-lived credential that outlives it or a password reset.
  • Timeouts. It expires after SESSION_KEY_IDLE_TIMEOUT_MINUTES (default 30) without a request, or SESSION_KEY_MAX_LIFETIME_MINUTES (default 720) after sign-in, whichever comes first.
  • Every instance agrees. Each request with a session key is checked against the database, not a cache, so an expired or revoked session key is refused on every instance at once. Keep the instances' clocks synchronized; expiry is measured with each instance's clock.
  • Ending a session. The holder signs out with DELETE /api/api-keys/current. An administrator signs a person out everywhere with DELETE /api/users/{username}/session-keys. Setting, changing, or resetting a password, and enrolling in MFA, also revoke the user's session keys. Long-lived keys are never affected.

Multi-factor authentication

Philter supports TOTP codes from an authenticator app. See Multi-factor authentication for the endpoints.

  • Settings. The mfaAvailable setting lets users enroll, and mfaRequired makes every user who signs in enroll. A user who is already enrolled is always asked for a code, even if mfaAvailable is later turned off.
  • Enrollment. Starting it takes the user's current password, so a stolen key cannot enroll an authenticator of its own and lock the person out. A user without a password cannot enroll. It takes effect only once a code for the new secret is confirmed. The secret is encrypted at rest with PHILTER_ENCRYPTION_KEY and shown only when enrollment starts.
  • Sign-in. For an enrolled user, the password returns a single-use challenge that expires in five minutes, not a key. The challenge and a code are exchanged for the key at POST /api/sign-in/mfa. A key is never issued on the password alone, and any attempt uses up the challenge.
  • Codes. Each code is accepted once. Five consecutive bad codes lock the user until an administrator unlocks them or removes the enrollment.
  • What a right password reveals. For an enrolled user, a right password returns a challenge, or 403 with the reason mfa_locked while their MFA is locked, rather than the 401 a wrong one gets. Whoever sends it learns that the password is right, as with any sign-in in two steps. The second factor is what protects the account, so treat a password that may be known to someone else as compromised.
  • Removal. A user removes their own enrollment with a valid code, so a stolen key cannot turn MFA off. An administrator removes another user's, for a person who has lost their device.

Lockout and rate limiting

  • Username lockout. SIGN_IN_MAX_FAILURES (default 5) failed sign-ins within SIGN_IN_LOCKOUT_MINUTES (default 15) lock the username for that long. An account's failures and lock are stored with the user, so they cannot be pushed out of a cache. A username with no account is counted and locked the same way, in the cache, so a lock says nothing about which usernames exist. A locked username is refused before its password is checked, even if it is right, and the lock clears on its own. An administrator can clear it early with POST /api/users/{username}/sign-in/unlock. Attempts are counted before their passwords are checked, so parallel guesses get no more checks than the limit.
  • Rate limiting. Each client address may make SIGN_IN_RATE_LIMIT_PER_MINUTE (default 20) sign-in requests a minute. IPv6 clients are counted by their /64, since one network holds that many addresses. The address follows the trusted-proxy rules. Behind a load balancer, or with a user interface such as Philter UI on another host, list its address in TRUSTED_PROXIES, or every client is counted as it and shares one limit.
  • Changing a password and enrolling in MFA. A wrong current password in PUT /api/users/me/password or POST /api/users/me/mfa counts as a failed sign-in, so a stolen key cannot be used to guess its user's password.
  • Across instances. Account lockout is shared by every instance through the database. The rate limit, and the count for usernames with no account, are shared through the Valkey/Redis cache that several instances require.

See Lockout and rate limiting for the responses.

Network placement

Lockout counts failures per username, from any address, so anyone who can reach sign-in can lock out a username they know, such as admin, by failing to sign in SIGN_IN_MAX_FAILURES times every SIGN_IN_LOCKOUT_MINUTES. This blocks password sign-in only: a long-lived API key keeps working, and an administrator can clear the lock, but it returns while the failures continue. Philter does not lock by address instead, since that would give an attacker with many addresses many more guesses. Limit who can reach sign-in at the network:

  • Allow only the user interface to reach Philter. With Philter UI or another interface, only its server calls POST /api/sign-in. A firewall rule or network policy that admits only that server, and the automation that uses API keys, keeps everyone else away from Philter's sign-in.
  • Protect the interface's sign-in. People sign in through the interface, so put it behind a VPN, single sign-on at a reverse proxy, or a web application firewall that rate limits its sign-in page. Do not expose it to the internet without one.
  • Set TRUSTED_PROXIES to the interface's address, so Philter records and rate limits each person's own address rather than the interface's.
  • Watch the audit log. Repeated sign_in_locked events for one username, especially admin, mean someone is trying its password or locking it out. Each records the client address.

Running a user interface

A user interface that signs people in through Philter should:

  • Keep each person's session key on its own server, never in the browser.
  • End its own session for the person when Philter rejects the key, which happens when the key expires or is revoked.
  • Pass the password straight to Philter, and never store or log it.
  • Serve its users over HTTPS and reach Philter over HTTPS.
  • Sign the person out with DELETE /api/api-keys/current when they sign out of the interface.
  • Show the person their sessions with GET /api/api-keys?session=true, which gives each one's client address, user agent, and last use and marks the current one, and let them sign out the others with DELETE /api/users/me/session-keys.

Settings

Setting Where Default
PASSWORD_SIGN_IN_ENABLED Environment false
SESSION_KEY_IDLE_TIMEOUT_MINUTES Environment 30
SESSION_KEY_MAX_LIFETIME_MINUTES Environment 720
SIGN_IN_MAX_FAILURES Environment 5
SIGN_IN_LOCKOUT_MINUTES Environment 15
SIGN_IN_RATE_LIMIT_PER_MINUTE Environment 20
mfaAvailable Settings API false
mfaRequired Settings API false

Audit events

None of these records a password, a password hash, an MFA secret, or a code. They are security events and cannot be switched off. See Auditing.

Event Recorded when
sign_in_succeeded A person signed in.
sign_in_failed A sign-in or MFA code was refused.
sign_in_locked Repeated failures locked a username.
sign_in_unlocked An administrator cleared a user's sign-in lock.
sign_in_rate_limited A client address went over the rate limit, once per minute.
user_password_set A user without a password was given one.
user_password_changed A user changed their own password.
user_password_reset An administrator replaced a user's password.
user_mfa_enrolled A user confirmed MFA enrollment.
user_mfa_removed A user's MFA was removed.
user_mfa_locked Five bad codes locked a user's MFA.
user_mfa_unlocked An administrator unlocked a user's MFA.
api_key_created A session key was issued, with session: true in the details.
api_key_expired A session key passed its idle timeout or maximum lifetime.
api_key_deleted A session key was revoked, with the reason in the details.