Skip to content

[codex] fix reborn google oauth decode and preview host login - #5388

Merged
serrrfirat merged 3 commits into
mainfrom
codex/fix-google-oauth-id-token-decode
Jun 28, 2026
Merged

serrrfirat merged 3 commits into
mainfrom
codex/fix-google-oauth-id-token-decode

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Jun 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Fix Reborn WebUI Google SSO id_token decoding for real Google RS256 tokens after the jsonwebtoken 10.x bump.
  • Canonicalize /auth/login/{provider} to IRONCLAW_REBORN_WEBUI_BASE_URL before creating pending OAuth state, so Railway preview/custom domains cannot send the browser to a callback host that cannot see the minted state.
  • Add redacted Google token-endpoint diagnostics and restore the previous 60-second ID-token expiration leeway.
  • Update caller-level Google OAuth route tests, provider token tests, and deployment/docs guidance.

Root Cause

Initial failure: Google returns RS256 ID tokens. The provider disabled signature verification but still called jsonwebtoken::decode with DecodingKey::from_secret(&[]). With jsonwebtoken 10.4.0 plus the aws_lc_rs crypto backend, decode still builds a verifier from the token header before skipping signature validation, so RS256 tokens paired with an HMAC empty key fail with InvalidKeyFormat.

Second failure found in preview: the Railway PR web URL was https://ironclaw-ironclaw-pr-5388.up.railway.app, but the OAuth callback configured in the generated Google authorization URL was https://ironclaw-ci-preview.up.railway.app/auth/callback/google. A live fake-code flow that started on the PR web host redirected to the callback host and returned /v2?login_error=invalid_state, proving the pending state was minted on a host/process the callback could not read. The login route now redirects to the configured canonical base URL before state creation.

The dependency break was exposed by #5271, which bumped jsonwebtoken to 10.4.0. The fragile empty-key pattern originated in the original WebUI v2 Google SSO implementation.

Validation

  • cargo fmt --check
  • cargo test -p ironclaw_reborn_webui_ingress --all-features --test google_oauth_routes -- --nocapture
  • cargo test -p ironclaw_reborn_webui_ingress --all-features --lib exchange_code_ -- --nocapture
  • CARGO_TARGET_DIR=target/codex-clippy-1.95 RUSTC=/Users/firatsertgoz/.rustup/toolchains/1.95.0-aarch64-apple-darwin/bin/rustc /Users/firatsertgoz/.rustup/toolchains/1.95.0-aarch64-apple-darwin/bin/cargo-clippy clippy -p ironclaw_reborn_webui_ingress --all-features --tests -- -D warnings

@coderabbitai

coderabbitai Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 49054a1e-9345-498e-b216-e05074d292f9

📥 Commits

Reviewing files that changed from the base of the PR and between 75bcb67 and 676b5a3.

📒 Files selected for processing (6)
  • crates/ironclaw_reborn_cli/src/commands/serve_sso.rs
  • crates/ironclaw_reborn_webui_ingress/CLAUDE.md
  • crates/ironclaw_reborn_webui_ingress/src/auth/google.rs
  • crates/ironclaw_reborn_webui_ingress/src/auth/routes.rs
  • crates/ironclaw_reborn_webui_ingress/tests/google_oauth_routes.rs
  • docs/reborn/deploy-reborn-cli-docker.md

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • OAuth login now consistently redirects users to the app’s canonical sign-in URL when they arrive via a non-canonical domain.
  • Bug Fixes

    • Improved handling of OAuth credentials with extra whitespace, reducing configuration issues from pasted secrets.
    • Login flow now tolerates small clock differences during token validation.
    • Unsafe login redirects are no longer echoed back in the sign-in flow.
  • Documentation

    • Clarified setup guidance for choosing and registering the correct public login domain.

Walkthrough

Three security hardening changes: (1) login_handler now checks the Host header against base_url and issues a temporary redirect before writing any pending-flow state; (2) Google ID-token decoding is centralized into decode_google_id_token enforcing alg==RS256, audience, and issuer with a fixed expiry leeway; (3) non_empty_env trims whitespace from OAuth credentials and logs redacted diagnostics at startup.

Changes

Canonical Host Redirect for Login Initiation

Layer / File(s) Summary
Host canonicalization helpers and error logging
crates/ironclaw_reborn_webui_ingress/src/auth/routes.rs
Adds request_host, request_host_matches_base, canonical_login_location, default_port private helpers, and extends log_oauth_error with error_kind_for mapping.
login_handler canonical-host gate
crates/ironclaw_reborn_webui_ingress/src/auth/routes.rs
login_handler gains HeaderMap extraction; before any state-store write, calls canonical_login_location and returns Redirect::temporary on host mismatch.
Integration tests: non-canonical host and unsafe redirect_after
crates/ironclaw_reborn_webui_ingress/tests/google_oauth_routes.rs
Two Tokio tests assert TEMPORARY_REDIRECT to canonical URL on wrong Host, and omission of unsafe redirect_after from the canonical redirect location.
CLAUDE.md invariant and deploy docs
crates/ironclaw_reborn_webui_ingress/CLAUDE.md, docs/reborn/deploy-reborn-cli-docker.md
CLAUDE.md adds the canonical-host security invariant; deploy doc clarifies canonical domain selection for IRONCLAW_REBORN_WEBUI_BASE_URL.

Google ID-token decoding hardening

Layer / File(s) Summary
ID-token claims types and decode helper
crates/ironclaw_reborn_webui_ingress/src/auth/google.rs
Introduces ID_TOKEN_EXPIRY_LEEWAY_SECS, GoogleIdTokenAudience enum with contains, and decode_google_id_token enforcing RS256/aud/iss via insecure_decode.
exchange_code error logging and decode wiring
crates/ironclaw_reborn_webui_ingress/src/auth/google.rs
Non-success token-endpoint path now warns with %status/%safe_error; success path wires decode_google_id_token and leeway-adjusted exp comparison.
ID-token test helpers and new cases
crates/ironclaw_reborn_webui_ingress/src/auth/google.rs
make_id_token_with_algorithm allows choosing header alg; new test asserts leeway acceptance; existing algorithm-rejection test updated.

OAuth env credential whitespace trimming

Layer / File(s) Summary
non_empty_env trimming and redacted startup logging
crates/ironclaw_reborn_cli/src/commands/serve_sso.rs
non_empty_env trims whitespace and warns on change; log_provider_config logs name, client id, and secret length only.
Trimming regression tests
crates/ironclaw_reborn_cli/src/commands/serve_sso.rs
Tests verify trimming behavior and that whitespace-padded credentials still yield a valid sso_startup_config_from_env.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • think-in-universe
  • loopstring

Poem

A rogue Host tries to sneak through the gate,
But canonical_login_location won't wait —
RS256 or get lost, says the alg check,
Trailing newlines in secrets? Trimmed off the deck.
The state store stays clean, the OAuth flows right,
🔐 Security hardened, from morning to night.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 09:49 Destroyed
@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 28, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors Google ID token validation to bypass the jsonwebtoken signature validation configuration, avoiding the need for a dummy RSA key. It introduces a custom decode_google_id_token function that manually validates the algorithm, audience, and issuer. Feedback suggests addressing clock skew in the manual token expiration check, as the previous implementation automatically allowed a 60-second leeway.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

// directly from Google. We still validate `alg`, `aud`, `iss`,
// and `exp` explicitly so the dependency's crypto backend does
// not need a fake RSA key just to parse Google's RS256 token.
let claims = decode_google_id_token(&id_token, &self.client_id)?;

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.

medium

The manual expiration check (claims.exp <= now) performed after decoding does not account for clock skew. Previously, jsonwebtoken::Validation::default() was used, which automatically allowed a default leeway of 60 seconds to accommodate minor clock differences between the host and Google's servers.

Without any leeway, minor clock drift can cause legitimate tokens to be rejected immediately. Consider adding a small leeway (e.g., 60 seconds) to the expiration check (e.g., claims.exp + 60 <= now) or validating the expiration inside decode_google_id_token with a leeway.

@railway-app

railway-app Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5388 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jun 28, 2026 at 7:47 pm

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 12:09 Destroyed
@github-actions github-actions Bot added scope: channel/web Web gateway channel size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules and removed size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules labels Jun 28, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 14:38 Destroyed
@github-actions github-actions Bot added the scope: docs Documentation label Jun 28, 2026
@serrrfirat serrrfirat changed the title [codex] fix reborn google oauth id token decode [codex] fix reborn google oauth decode and preview host login Jun 28, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 18:44 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jun 28, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 19:27 Destroyed
serrrfirat and others added 2 commits June 28, 2026 22:47
A Google login that reached the callback (valid client_id + registered
redirect_uri, so authorization succeeded) failed at the token exchange
with `invalid_client`, surfacing as `login_error=exchange_failed`
("Could not complete sign-in with the provider"). The client_secret is
the one credential a provider checks only at the token endpoint, never
at authorization, so a malformed secret sails through the login redirect
and is rejected only at code exchange.

Root cause: `non_empty_env` filtered on `.trim()` but returned the RAW
value, so an `IRONCLAW_REBORN_WEBUI_*_CLIENT_SECRET` pasted into a
deployment dashboard with a trailing newline / surrounding space was
forwarded to the provider verbatim.

- `non_empty_env` now returns the trimmed value and warns (naming the
  variable) when it had to strip whitespace, so a malformed secret is
  visible at boot.
- Add a redacted startup diagnostic (provider, client_id, secret length
  — never the secret) so a wrong/mismatched secret is diagnosable from
  boot logs without capturing a live login.
- Regression tests: `non_empty_env_trims_surrounding_whitespace`
  (helper) and `whitespace_padded_oauth_credentials_still_configure_provider`
  (through the `sso_startup_config_from_env` caller).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@serrrfirat
serrrfirat force-pushed the codex/fix-google-oauth-id-token-decode branch from 51d3d2b to 676b5a3 Compare June 28, 2026 19:47
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5388 June 28, 2026 19:47 Destroyed
@github-actions github-actions Bot added size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules and removed size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules labels Jun 28, 2026
@serrrfirat
serrrfirat marked this pull request as ready for review June 28, 2026 20:00
@serrrfirat
serrrfirat merged commit e5da13d into main Jun 28, 2026
105 of 106 checks passed
@serrrfirat
serrrfirat deleted the codex/fix-google-oauth-id-token-decode branch June 28, 2026 20:00

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5388 — 676b5a3f Deployed Jun 28, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: channel/web Web gateway channel scope: docs Documentation size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant