Repository navigation
Conversation
There was a problem hiding this comment.
Pull request overview
Adds end-user documentation for configuring JWT-based authentication when Semaphore is deployed behind an authenticating reverse proxy (e.g., Pomerium / Cloudflare Access), and exposes the new page in the Admin Guide sidebar.
Changes:
- Adds a new Admin Guide page describing JWT proxy auth flow, configuration (config file + env vars), and behavior details.
- Registers the new doc in the Docusaurus sidebar navigation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
sidebars.js |
Adds the new JWT proxy auth doc to the Admin Guide sidebar items. |
docs/admin-guide/jwt-proxy-auth.md |
New documentation page covering JWT proxy auth setup, options, and JWKS behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 1. The reverse proxy authenticates the user and adds a signed JWT to a configured HTTP header. | ||
| 2. Semaphore checks for this header on every request. If present, the token is validated against the JWKS endpoint. | ||
| 3. If the token is valid, the user is loaded by email. If no matching user exists, an external user is created automatically (same as OIDC). | ||
| 4. If the header is present but the token is invalid, Semaphore returns `401 Unauthorized` immediately -- existing bearer token and session auth are not attempted. | ||
| 5. If the header is absent, normal authentication (bearer token, session cookie) proceeds as usual. |
There was a problem hiding this comment.
JWT-in-header auth is sensitive to header spoofing / DoS if the upstream proxy forwards client-supplied values. Consider adding a short Security considerations note stating that the reverse proxy must strip any incoming JWT header from clients and only set/overwrite it after authentication (and ensure Semaphore isn’t reachable directly, bypassing the proxy).
There was a problem hiding this comment.
Added a "Security considerations" section: Semaphore must only be reachable through the proxy, the proxy must drop any client-supplied JWT header and set it only after authenticating, and audience should be set so tokens the proxy issued for other applications are rejected. Fixed in 8cd2d19.
|
|
||
| ## JWKS availability | ||
|
|
||
| Semaphore fetches the JWKS on startup. If the JWKS endpoint is unreachable, the server still starts and retries in the background (5s, 10s, 30s, then every 60s). JWT auth returns an error until the JWKS is loaded. |
There was a problem hiding this comment.
The JWKS retry schedule is documented with exact timings (5s, 10s, 30s, 60s). Unless these intervals are intended as a stable contract, consider describing it more generally (for example, “retries with backoff in the background”) or linking to a config/constant reference, so the docs don’t become stale if the implementation changes.
| Semaphore fetches the JWKS on startup. If the JWKS endpoint is unreachable, the server still starts and retries in the background (5s, 10s, 30s, then every 60s). JWT auth returns an error until the JWKS is loaded. | |
| Semaphore fetches the JWKS on startup. If the JWKS endpoint is unreachable, the server still starts and retries in the background with increasing backoff. JWT auth returns an error until the JWKS is loaded. |
There was a problem hiding this comment.
The schedule was also out of date: the implementation now uses keyfunc, which refreshes the key set hourly, and early when a token uses an unknown key ID, at most once every five minutes. The section names those two intervals because they are keyfunc's defaults and Semaphore does not override them. The old retry schedule is gone. Fixed in 8cd2d19.
Document JWT proxy authentication for reverse proxy setups (Pomerium, Cloudflare Access, etc). Covers auth flow, config file/env vars, Docker example, options table, supported algorithms, and JWKS retry. Ref: semaphoreui/semaphore#3719
Adds Logout section explaining why a Semaphore-only logout leaves the reverse proxy session intact, and how signout_url redirects to the proxy's signout endpoint after clearing the local session.
The page now lives next to the local, LDAP and OpenID Connect pages and is listed in the Authentication section's tables. It also matches the current code: users are found by the external identity for the token's sub claim and then by email as external_auth_email_matching allows, a token without sub or exp is rejected, and the JWKS is refreshed hourly and on unknown key IDs rather than on a 5s-60s retry schedule. The Docker example uses sqlite instead of the deprecated bolt driver. A new Security considerations section says the proxy must be the only way to reach Semaphore, must drop a client-supplied JWT header, and that audience should be set so tokens for other applications behind the same proxy are rejected. Headings get explicit ids, and the page and the Authentication README changes are translated into all ten locales.
Document JWT proxy authentication for reverse proxy
setups (Pomerium, Cloudflare Access, etc).
Covers auth flow, config file/env vars, Docker example,
options table, supported algorithms, and JWKS retry.
Ref: semaphoreui/semaphore#3719