Skip to content

Support Oauth 2.0#19

Open
mwfj wants to merge 7 commits intomainfrom
support-oauth-2.0
Open

Support Oauth 2.0#19
mwfj wants to merge 7 commits intomainfrom
support-oauth-2.0

Conversation

@mwfj
Copy link
Copy Markdown
Owner

@mwfj mwfj commented Apr 15, 2026

OAuth 2.0 Token Validation: Design Spec + Phase 1 Foundation (Steps 1–2) with jwt-cpp Adoption

Summary

This PR lands the reviewer-approved design spec (r1 → r5, 5 revisions) and the additive foundation — data structures, pure utilities, and vendored jwt-cpp v0.7.1 — for gateway-level OAuth 2.0 token validation.

No runtime behavior change. ProxyConfig::auth is declared but not yet read; HttpRequest::auth is always empty; AuthManager / JwtVerifier / middleware wiring are deferred to follow-up PRs per the phase split in the spec.

Branch: support-oauth-2.0. 18 files changed, +5691 / −4 LoC (bulk is the vendored jwt-cpp headers, MIT, header-only).


Design spec

Scope: Token-validator only (not a BFF / authorization-code-flow client). Supports multi-issuer deployments (Google, OpenAI, future own-IdP) with JWT verification and RFC 7662 introspection modes per issuer. Opt-in per proxy route via upstreams[].proxy.auth OR top-level auth.policies[] with applies_to prefixes.

Review history (condensed)

Revision Scope Outcome
r1 Initial spec 4 findings from reviewer (route surface, middleware contract, header injection, HS256)
r2 Addressed r1 findings Auth attached via UpstreamConfig::ProxyConfig (not a new routes[] table); mutable-field pattern for req.auth; outbound header injection in HeaderRewriter; HS256 dropped from v1
r3 4 follow-up findings ProxyConfig::operator== excludes auth with same-PR AuthManager::Reload wiring rule; shared_ptr<const AuthForwardConfig> snapshot model; programmatic-route scope clarified; middleware-vs-route-lookup ordering fixed
r4 Library-vs-scratch evaluation Adopt jwt-cpp v0.7.1 immediately (not deferred); delete Phase 1's from-scratch jwt_decode.{h,cc} in the same PR
r5 3 follow-up findings on r4 Vendor base.h + define JWT_DISABLE_PICOJSON; §9 item 16 exception containment; rewrite alg:none rationale as property of OUR code (not the library)
final Doc cleanups §6.3 AuthContext snippet aligned with real header; §9 item 16 parse_jwks throw maps to undetermined/503, not invalid_token

Key design decisions — top-of-mind (Appendix B has 30 rows total)

Decision Choice
Role Validator only — not an OAuth client / BFF
Protection model Opt-in per route (matches rate-limit / CB conventions; matches Envoy/Kong/Nginx/Traefik)
Outbound IO Reuse UpstreamManager for IdP calls (inherits TLS, pool, CB, retry, metrics)
Config shape Named issuers + per-proxy reference AND top-level auth.policies[] with applies_to
JWT decode + verify jwt-cpp v0.7.1 vendored (MIT, header-only, OpenSSL-backed)
Algorithms (v1) RS256/RS384/RS512/ES256/ES384 — asymmetric only (HS256 deferred)
alg: none defense Property of OUR code — JwtVerifier::BuildVerifier() never calls allow_algorithm(jwt::algorithm::none{}); ConfigLoader rejects "none" at load; mandatory test asserts rejection
Exception containment Every jwt-cpp call wrapped in try/catch(std::exception) → translated to result types (401 invalid_request / 401 invalid_token / 503 undetermined)
Introspection cache key HMAC-SHA256(random process-local key, token) → 128-bit hex (non-configurable security invariant)
AuthForwardConfig lifetime shared_ptr<const> snapshot, per-call to RewriteRequest — reload-safe
proxy.auth reload ProxyConfig::operator== excludes auth AND AuthManager::Reload wiring in same PR (mirrors UpstreamConfig::operator== / CircuitBreakerManager::Reload discipline)
Failure mode Fail-closed default (503 + Retry-After for undetermined), per-policy on_undetermined: "allow" opt-out

Industry comparison

§2 of the spec anchors every design choice against Nginx (OSS + Plus), Envoy (jwt_authn / ext_authz / oauth2), Kong (jwt / oauth2 / openid-connect), Traefik (ForwardAuth), and oauth2-proxy. Consensus: microservice-focused gateways split token validation from authorization-code flow into separate modules, and all of them use opt-in per-route protection for stateless API tokens.


Changes in this PR

Step 1 — Data structures (additive)

New headers:

  • include/auth/auth_context.hAuthContext struct (issuer, subject, scopes, claims, policy_name, SENSITIVE raw_token with "never log" contract, undetermined flag).
  • include/auth/auth_config.h — full config type hierarchy: IntrospectionConfig, IssuerConfig, AuthPolicy, AuthForwardConfig, AuthConfig, all with operator== for reload comparison.

Modified files:

  • include/http/http_request.h — added mutable std::optional<auth::AuthContext> auth; (same mutable-field pattern as params, client_ip, async_cancel_slot). Cleared in Reset() for keep-alive correctness.
  • include/config/server_config.h — added auth::AuthPolicy auth to ProxyConfig with explicit operator== exclusion plus rule-cite comment pointing at DEVELOPMENT_RULES.md and design spec §11.1. Added auth::AuthConfig auth to ServerConfig.

Rationale for ProxyConfig::operator== excluding auth: same-PR discipline from DEVELOPMENT_RULES.md ("Live-reloadable config fields in restart-required equality operators — ordering matters"). Both halves (exclusion AND AuthManager::Reload wiring) must land together. This PR ships the exclusion; the wiring lands in the follow-up PR that introduces AuthManager.

Step 2 — Pure utilities (additive)

New files:

  • include/auth/token_hasher.h + server/token_hasher.cc
    • TokenHasher::Hash() — HMAC-SHA256 with 128-bit truncation, hex-encoded. Returns std::optional<std::string>nullopt on HMAC failure (per r3 finding: "" would collide distinct failing tokens on the same cache key, a confidentiality bug).
    • GenerateHmacKey() — OpenSSL RAND_bytes (fails-closed on RNG failure).
    • LoadHmacKeyFromEnv() — auto-detect base64url-32-bytes-or-raw; wrapped in try/catch(std::exception) per r5 §9 item 16 — jwt::base::decode throws on invalid input, and we fall through to raw-bytes interpretation rather than aborting server startup.
  • include/auth/auth_policy_matcher.h + server/auth_policy_matcher.cc
    • AppliedPolicy struct ({prefix, AuthPolicy}) + AppliedPolicyList vector.
    • FindPolicyForPath() — longest-prefix match. Empty-prefix catch-all supported. Case-sensitive. Documented as plain prefix (not segment-safe) — e.g., /admin matches /administrator/foo; operators use trailing slash /admin/ for segment-safe narrowing.
    • ValidatePolicyList() — detects exact-prefix collisions with human-readable errors.
  • include/auth/auth_claims.h + server/auth_claims.cc
    • ExtractScopes() — handles scope (space-separated string), scp (array), scopes (Azure AD style).
    • PopulateFromPayload() — builds AuthContext from JWT payload + operator-requested claim keys; rejects payloads missing iss or sub. Array/object claims are deliberately skipped at this layer (flattening is a Phase 3 HeaderRewriter concern because serialization depends on upstream expectations).
    • HasRequiredScopes() / MatchesAudience() — enforcement helpers.

jwt-cpp adoption (per r4 / r5)

Vendoredthird_party/jwt-cpp/ (v0.7.1, MIT, header-only):

  • include/jwt-cpp/jwt.h (~4228 LoC)
  • include/jwt-cpp/base.h (~356 LoC — required because jwt.h includes it, and token_hasher calls jwt::base::decode directly)
  • include/jwt-cpp/traits/nlohmann-json/defaults.h + traits.h
  • LICENSE (MIT)

NOT vendored: picojson/picojson.h — suppressed via -DJWT_DISABLE_PICOJSON macro. We use nlohmann/json.

Makefile changes:

  • Added -Ithird_party/jwt-cpp/include and -DJWT_DISABLE_PICOJSON to CXXFLAGS.
  • JWT_CPP_DIR macro + 4 jwt-cpp headers added to AUTH_HEADERS for dependency tracking (so a bump-jwt-cpp PR correctly invalidates the whole build).

Deleted files (superseded by jwt-cpp):

  • include/auth/jwt_decode.h
  • server/jwt_decode.cc
  • These contained our from-scratch Base64UrlEncode/Decode, JwtDecoded, and Decode(). All superseded by jwt::decode<traits>(token) + jwt::base::{encode,decode}<jwt::alphabet::base64url>(). Preserves the strict-padding base64url fix from the earlier code review — jwt-cpp's decoder is also strict by construction.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6422c921a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/auth_claims.cc
Comment thread include/config/server_config.h
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements a comprehensive OAuth 2.0 authentication layer, featuring JWT decoding, claim extraction, and path-based policy matching using a longest-prefix algorithm. It also introduces HMAC-SHA256 hashing for secure introspection caching and integrates the authentication context directly into the HTTP request lifecycle. Feedback focuses on improving the implementation by refactoring duplicated scope extraction logic, optimizing scope validation performance through the use of a hash set, and replacing hand-rolled Base64Url functions with OpenSSL's native library implementations for enhanced security and maintainability.

Comment thread server/auth_claims.cc
Comment thread server/auth_claims.cc
Comment thread server/jwt_decode.cc Outdated
@mwfj
Copy link
Copy Markdown
Owner Author

mwfj commented Apr 15, 2026

Due to we change the design plan, where use the jwt-cpp as the jwt library to reference it.
The previous review comment is outdate, will be re-launch the review for the newest code applied.

@mwfj
Copy link
Copy Markdown
Owner Author

mwfj commented Apr 15, 2026

@codex review

@mwfj
Copy link
Copy Markdown
Owner Author

mwfj commented Apr 15, 2026

/gemini review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d8068f3d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Makefile
Comment thread server/auth_claims.cc Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the foundational components for an authentication layer, supporting OAuth 2.0 token validation and JWT processing. It adds configuration structures for issuers and policies, logic for extracting claims and scopes, and a path-based policy matcher using longest-prefix semantics. Additionally, it implements a secure HMAC-SHA256 token hashing mechanism for caching and integrates the authentication context into the HTTP request lifecycle. Feedback focuses on improving the robustness of claim extraction by explicitly handling large unsigned integers and refactoring duplicated logic in scope extraction.

Comment thread server/auth_claims.cc
Comment thread server/auth_claims.cc Outdated
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