Skip to content

docs(auth): add JWT proxy auth page - #123

Open
abh wants to merge 3 commits into
semaphoreui:mainfrom
abh:jwt-auth
Open

abh wants to merge 3 commits into
semaphoreui:mainfrom
abh:jwt-auth

Conversation

@abh

@abh abh commented Mar 24, 2026

Copy link
Copy Markdown

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

Copilot AI review requested due to automatic review settings March 24, 2026 05:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +9 to +13
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.

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/admin-guide/jwt-proxy-auth.md Outdated

## 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.

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.

@abh abh Oct 12, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

abh added 2 commits October 12, 2026 01:56
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants