Skip to content

Add OAuth 2.0 / JWT Bearer token authentication - #664

Open
jakeichikawasalesforce wants to merge 17 commits into
masterfrom
jwt-auth-integration
Open

Add OAuth 2.0 / JWT Bearer token authentication#664
jakeichikawasalesforce wants to merge 17 commits into
masterfrom
jwt-auth-integration

Conversation

@jakeichikawasalesforce

@jakeichikawasalesforce jakeichikawasalesforce commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds OAuth 2.0 / JWT Bearer token authentication as an alternative (or
complement) to TabPy's existing password-file basic auth. This lets
per-user credentials issued by an external identity provider (IdP) be
used instead of, or alongside, a single shared password-file credential.

  • Validates incoming JWTs against a configured IdP: signature (via JWKS),
    issuer, audience, expiry/nbf, and optionally required scopes.
  • New config settings: TABPY_OAUTH_ENABLED, TABPY_OAUTH_ISSUER,
    TABPY_OAUTH_JWKS_URI, TABPY_OAUTH_AUDIENCE,
    TABPY_OAUTH_REQUIRED_SCOPES (optional), TABPY_OAUTH_LOG_USER
    (optional, default false).
  • Basic auth and OAuth can be enabled at the same time; the method used
    is selected per-request based on the Authorization header scheme
    (Basic vs Bearer), so both can coexist on the same server.
  • TABPY_OAUTH_ISSUER and TABPY_OAUTH_JWKS_URI are required to use
    https:// — the JWKS response is the trust anchor for verifying JWT
    signatures, so fetching it over plain HTTP would let an on-path
    attacker substitute their own keys. TABPY_OAUTH_JWKS_URI is also
    resolved at startup and rejected unless it resolves only to public
    (globally routable) addresses, to prevent SSRF via a misconfigured or
    attacker-supplied host.
  • The JWT signing algorithm is always pinned to the JWKS-resolved key's
    algorithm, never trusted from the token header, to prevent
    algorithm-confusion attacks (e.g. RS256→HS256 substitution, alg=none).
  • JWKS fetches are cached and reused per-IdP; a forced refresh (triggered
    by an unrecognized kid) is rate-limited so repeated bogus kids from
    an unauthenticated caller can't force unbounded blocking network fetches
    on TabPy's single IO-loop thread.
  • 401 responses always return the same generic body regardless of why a
    JWT was rejected, so an unauthenticated caller can't enumerate failure
    reasons.
  • Docs updated in docs/server-config.md.

Testing

  • Added unit tests covering JWT validation (test_jwt_auth.py), OAuth
    request handling end-to-end (test_oauth_handler.py), and OAuth config
    validation (test_config.py).
  • Full unit test suite passes locally.
  • Manually verified against a local self-signed-HTTPS JWKS endpoint:
    valid token accepted, expired/wrong-issuer/wrong-audience tokens
    rejected with 401, and the request-context log line correctly includes
    the authenticated TabPy user when TABPY_LOG_DETAILS and
    TABPY_OAUTH_LOG_USER are both enabled.

@jakeichikawasalesforce
jakeichikawasalesforce marked this pull request as ready for review August 3, 2026 19:56
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.

1 participant