Skip to content

feat: add oidc strict state/nonce mode#4191

Merged
eric-intuitem merged 13 commits into
mainfrom
CA-1696-improve-oidc-security
May 29, 2026
Merged

feat: add oidc strict state/nonce mode#4191
eric-intuitem merged 13 commits into
mainfrom
CA-1696-improve-oidc-security

Conversation

@tchoumi313
Copy link
Copy Markdown
Contributor

@tchoumi313 tchoumi313 commented May 22, 2026

  • extending the state to more thatn 36 characters
  • adding nonce parameter generation
  • making state and nonce the standard flow for all oidc

Summary by CodeRabbit

  • Security & Stability

    • Strengthened OpenID Connect login flow with end-to-end nonce validation and improved redirect handling for OIDC sign-ins to prevent replay/mismatch issues.
    • Redacted sensitive OAuth/OpenID query parameters from logged request URLs to protect credentials and tokens.
  • Localization

    • Updated Czech translation text for improved clarity and consistency.

Review Change Stack

@tchoumi313 tchoumi313 self-assigned this May 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

Implements end-to-end OIDC nonce handling: generator, session stash with eviction, redirect helper injecting nonce, and a callback adapter that enforces nonce equality. Integrates the redirect into SSO routing, adds logging redaction for sensitive query params, and updates one Czech localization entry.

Changes

OIDC Nonce/State Validation

Layer / File(s) Summary
Token format and generator
backend/iam/sso/oidc/views.py
Defines OIDC state/nonce token format and generator.
Nonce-validating adapter
backend/iam/sso/oidc/views.py
Adds NonceValidatingOpenIDConnectAdapter.complete_login that decodes id_token, pops expected nonce from session by state, and raises OAuth2Error on missing/mismatched nonce.
Redirect helper and nonce stashing
backend/iam/sso/oidc/views.py
Adds oidc_redirect to generate state_id/nonce, stash expected nonce in session with eviction, set client state, and inject nonce into auth params; implements _stash_oidc_nonce.
Login view and callback wiring
backend/iam/sso/oidc/views.py
Login initiation now resolves provider and delegates to oidc_redirect; adds headless-only guard; callback now instantiates the nonce-validating adapter.

SSO Provider Routing

Layer / File(s) Summary
RedirectToProviderView branching
backend/iam/sso/views.py
Imports oidc_redirect and routes provider.id == "openid_connect" through it (passing process, next_url, headless=True); other providers unchanged.

Logging Redaction

Layer / File(s) Summary
Sensitive query param redaction
backend/ciso_assistant/settings.py
Adds _SENSITIVE_QUERY_PARAMS and redact_sensitive_query_params structlog processor to redact sensitive OAuth/OpenID parameters from logged request URLs.
Logger configuration and registration
backend/ciso_assistant/settings.py
Disables django.server default request logging and registers the redaction processor in structlog.configure.

Czech Localization

Layer / File(s) Summary
Czech Localization Message Update
frontend/messages/cs.json
Updates builderChangedOn text and reorders requirementWeight in the JSON fragment.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LoginView as Login View
  participant OidcRedirect as oidc_redirect
  participant Session
  participant OIDCProvider as OIDC Provider
  participant CallbackAdapter as NonceValidatingOpenIDConnectAdapter

  User->>LoginView: initiate OIDC login
  LoginView->>OidcRedirect: call oidc_redirect(provider, process, next_url)
  OidcRedirect->>OidcRedirect: generate state_id and nonce
  OidcRedirect->>Session: _stash_oidc_nonce(state_id, nonce)
  OidcRedirect->>OIDCProvider: redirect with state and nonce in auth params
  OIDCProvider->>User: user authenticates and returns id_token + state
  User->>CallbackAdapter: callback with id_token and state
  CallbackAdapter->>Session: pop expected nonce by state_id
  CallbackAdapter->>CallbackAdapter: decode id_token and compare nonce claim
  alt nonce matches
    CallbackAdapter->>User: complete_login (success)
  else nonce missing or mismatched
    CallbackAdapter->>User: raise OAuth2Error (failure)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A nonce I weave, small and neat,
Stashed in session, steady heartbeat,
Redirects carry state in tow,
Callback checks — let truth show.
Czech words shift, logs scrubbed with care,
Hop, review, and safe SSO everywhere.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add oidc strict state/nonce mode' accurately reflects the main objective of the PR: implementing strict OIDC state/nonce validation as the default for all OIDC integrations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch CA-1696-improve-oidc-security

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/iam/sso/oidc/views.py`:
- Around line 66-79: The code only performs nonce validation when id_token_str
is present, allowing strict-mode logins to bypass nonce checks if id_token is
missing; update the OIDC callback flow in the view handling id_token_str to
enforce strict-mode failure when no id_token is provided by checking for the
expected nonce even if id_token_str is falsy: retrieve state via
get_request_param(request, "state"), pop the expected_nonce from session using
f"{_STRICT_NONCE_SESSION_PREFIX}{state_id}", and if expected_nonce is not None
then either decode and validate nonce via self._decode_id_token(...) and compare
decoded.get("nonce") to expected_nonce (raising OAuth2Error("OIDC nonce
mismatch") and logging with provider=self.provider_id on mismatch) or, if
id_token_str is missing, immediately log and raise OAuth2Error to fail
strict-mode rather than continuing and setting data["id_token"].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70d4f4de-061c-48b0-971a-df7289068cce

📥 Commits

Reviewing files that changed from the base of the PR and between b322ede and 224b71d.

📒 Files selected for processing (7)
  • backend/iam/sso/oidc/views.py
  • backend/iam/sso/serializers.py
  • backend/iam/sso/views.py
  • frontend/messages/en.json
  • frontend/messages/fr.json
  • frontend/src/lib/components/Forms/ModelForm/SsoSettingForm.svelte
  • frontend/src/lib/utils/schemas.ts

Comment thread backend/iam/sso/oidc/views.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/messages/cs.json`:
- Line 1875: The translation string oidcStrictStateNonceHelpText currently lists
allowed URL-safe characters as "- . _ ~ ," including an erroneous comma; update
the message in frontend/messages/cs.json to remove the comma so the
allowed-symbols list reads "- . _ ~" (preserving spacing/quoting and Czech
wording), ensuring the key oidcStrictStateNonceHelpText is the only changed
text.

In `@frontend/messages/da.json`:
- Line 1887: The translation for key "oidcStrictStateNonceHelpText" lists an
incorrect comma in the allowed symbol set; update the string to remove the comma
so the symbol sequence matches the actual allowed characters (e.g. "_.-~") and
ensure the rest of the sentence remains unchanged.

In `@frontend/messages/el.json`:
- Line 2283: Update the Greek translation string for the OIDC help text key
"oidcStrictStateNonceHelpText" to remove the incorrect comma from the
allowed-symbols list so it describes only the symbols _. - ~ (e.g., change "A-Z,
a-z, 0-9 και τα σύμβολα - . _ ~ ," to "A-Z, a-z, 0-9 και τα σύμβολα _. - ~" or
similar grammatical order) ensuring the rest of the sentence and punctuation
remain unchanged.

In `@frontend/messages/et.json`:
- Line 2726: The translation for the key oidcStrictStateNonceHelpText includes
an incorrect comma in the allowed-symbol list; update that value so the allowed
symbols are reported as "_.-~" (or listed in the order used in docs) instead of
including a comma, ensuring the Estonian help text reflects the strict-mode
character set correctly for state and nonce.

In `@frontend/messages/hr.json`:
- Line 1795: Update the "oidcStrictStateNonceHelpText" string to remove the
comma from the documented allowed symbol set so it reads the allowed symbols as
"- . _ ~" (i.e., change "- . _ ~ ," to "- . _ ~"); ensure no other punctuation
is added or removed and keep the rest of the wording unchanged so the key
"oidcStrictStateNonceHelpText" accurately reflects the strict charset.

In `@frontend/messages/hu.json`:
- Line 1585: The help text for oidcStrictStateNonceHelpText incorrectly lists a
comma as an allowed symbol; update the Hungarian string to remove the comma from
the allowed-symbol list so it matches the strict charset (e.g., list only A-Z,
a-z, 0-9 and the symbols - . _ ~), preserving existing punctuation and spacing
in the JSON value for oidcStrictStateNonceHelpText.

In `@frontend/messages/ko.json`:
- Line 2377: The Korean translation for the key "oidcStrictStateNonceHelpText"
incorrectly lists a comma as an allowed URL-safe symbol; update the string to
remove the comma and list only the URL-safe symbols underscore, period, hyphen,
and tilde (e.g., "기호 _ . - ~") so it matches the English source and the PR
description.

In `@frontend/messages/lt.json`:
- Line 2569: The help text for the localization key oidcStrictStateNonceHelpText
incorrectly lists allowed symbols with spaces and a trailing comma (which may
suggest a comma is allowed); update the string to explicitly list the intended
symbol set as "_.-~" (no spaces or trailing comma) and adjust the surrounding
wording so it reads that allowed characters are A-Z, a-z, 0-9 and symbols
"_.-~", preserving the rest of the sentence and meaning.

In `@frontend/messages/nl.json`:
- Line 2164: The help text for oidcStrictStateNonceHelpText incorrectly lists a
comma as an allowed character; update the string to remove the comma from the
allowed-character list so it reads "A-Z, a-z, 0-9 and the symbols - . _ ~"
(i.e., remove the "," character from the symbols portion) in the
frontend/messages/nl.json entry for oidcStrictStateNonceHelpText.

In `@frontend/messages/pl.json`:
- Line 1760: The message value for "oidcStrictStateNonceHelpText" includes a
comma in the allowed-symbols list which contradicts strict mode; update the
string for the oidcStrictStateNonceHelpText key to list only the strict-mode
symbols (_.-~) instead of including a comma so the help text matches the
enforced character set.

In `@frontend/messages/tr.json`:
- Around line 1754-1755: The help text for oidcStrictStateNonceHelpText
currently lists allowed characters and mistakenly includes a comma in the symbol
set; update the string so the allowed symbols are shown accurately (A-Z, a-z,
0-9 and - . _ ~) without a trailing comma or wording that implies comma is
allowed, e.g., rephrase to explicitly state "URL-safe 40-character state and
nonce using characters A–Z, a–z, 0–9 and the symbols - . _ ~" and ensure the key
oidcStrictStateNonceHelpText is the one being updated.

In `@frontend/messages/uk.json`:
- Around line 2102-2103: The help text for oidcStrictStateNonceHelpText is
ambiguous about allowed characters; update the string so the allowed charset is
unambiguous by replacing the ",)" fragment that implies a comma is allowed with
the explicit symbol set "_.-~" (so the help text clearly lists A-Z, a-z, 0-9 and
the symbols _ . - ~) and keep the rest of the sentence intact; modify the value
for the key oidcStrictStateNonceHelpText accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e002445-be84-445d-826b-b0760ccb7be0

📥 Commits

Reviewing files that changed from the base of the PR and between 224b71d and 1d80df5.

📒 Files selected for processing (23)
  • frontend/messages/ar.json
  • frontend/messages/cs.json
  • frontend/messages/da.json
  • frontend/messages/de.json
  • frontend/messages/el.json
  • frontend/messages/es.json
  • frontend/messages/et.json
  • frontend/messages/hi.json
  • frontend/messages/hr.json
  • frontend/messages/hu.json
  • frontend/messages/id.json
  • frontend/messages/it.json
  • frontend/messages/ko.json
  • frontend/messages/lt.json
  • frontend/messages/nl.json
  • frontend/messages/pl.json
  • frontend/messages/pt.json
  • frontend/messages/ro.json
  • frontend/messages/sv.json
  • frontend/messages/tr.json
  • frontend/messages/uk.json
  • frontend/messages/ur.json
  • frontend/messages/zh.json
✅ Files skipped from review due to trivial changes (11)
  • frontend/messages/pt.json
  • frontend/messages/zh.json
  • frontend/messages/ur.json
  • frontend/messages/de.json
  • frontend/messages/es.json
  • frontend/messages/it.json
  • frontend/messages/sv.json
  • frontend/messages/ro.json
  • frontend/messages/ar.json
  • frontend/messages/hi.json
  • frontend/messages/id.json

Comment thread frontend/messages/cs.json Outdated
Comment thread frontend/messages/da.json Outdated
Comment thread frontend/messages/el.json Outdated
Comment thread frontend/messages/et.json Outdated
Comment thread frontend/messages/hr.json Outdated
Comment thread frontend/messages/lt.json Outdated
Comment thread frontend/messages/nl.json Outdated
Comment thread frontend/messages/pl.json Outdated
Comment thread frontend/messages/tr.json Outdated
Comment thread frontend/messages/uk.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
backend/iam/sso/oidc/views.py (1)

56-76: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require a matching nonce whenever one was stashed.

oidc_redirect() always stores an expected nonce, but this callback still succeeds if the provider returns no id_token, and it also only warns when the decoded id_token has no nonce claim. Both paths let the login complete without binding the callback to the original authorization request, which defeats the PR's strict nonce guarantee.

🔧 Minimal fix
-        data = {}
-        if fetch_userinfo or (not id_token_str):
-            data["userinfo"] = self._fetch_user_info(token.token)
-        if id_token_str:
-            decoded = self._decode_id_token(app, id_token_str)
-            state_id = get_request_param(request, "state")
-            expected_nonce = request.session.pop(
-                f"{_OIDC_NONCE_SESSION_PREFIX}{state_id}", None
-            )
-            if expected_nonce is not None:
-                returned_nonce = decoded.get("nonce")
-                if returned_nonce is None:
-                    logger.warning(
-                        "OIDC id_token has no nonce claim; skipping nonce validation",
-                        provider=self.provider_id,
-                    )
-                elif returned_nonce != expected_nonce:
-                    logger.error(
-                        "OIDC nonce mismatch",
-                        provider=self.provider_id,
-                    )
-                    raise OAuth2Error("OIDC nonce mismatch")
+        data = {}
+        state_id = get_request_param(request, "state")
+        expected_nonce = request.session.pop(
+            f"{_OIDC_NONCE_SESSION_PREFIX}{state_id}", None
+        )
+        if expected_nonce is not None and not id_token_str:
+            logger.error(
+                "OIDC id_token missing in strict nonce mode",
+                provider=self.provider_id,
+            )
+            raise OAuth2Error("OIDC id_token missing in strict nonce mode")
+        if fetch_userinfo or (not id_token_str):
+            data["userinfo"] = self._fetch_user_info(token.token)
+        if id_token_str:
+            decoded = self._decode_id_token(app, id_token_str)
+            if expected_nonce is not None and decoded.get("nonce") != expected_nonce:
+                logger.error(
+                    "OIDC nonce mismatch",
+                    provider=self.provider_id,
+                )
+                raise OAuth2Error("OIDC nonce mismatch")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/iam/sso/oidc/views.py` around lines 56 - 76, The flow must fail if a
nonce was stashed but the provider did not return a matching nonce: when you pop
expected_nonce from request.session (using _OIDC_NONCE_SESSION_PREFIX +
state_id) and expected_nonce is not None, require an id_token_str and a decoded
nonce; if id_token_str is missing throw OAuth2Error (not succeed), if
decoded.get("nonce") is None throw OAuth2Error (replace the current warning),
and keep the existing error+raise when returned_nonce != expected_nonce; use the
existing helpers _decode_id_token, get_request_param and keep
provider=self.provider_id in logs to locate the logic.
🧹 Nitpick comments (1)
frontend/messages/cs.json (1)

1874-1875: ⚡ Quick win

Keep locale key order stable; avoid reordering during text-only edits.

Line 1875 was moved next to the edited key. Please keep requirementWeight in its original position and limit this hunk to the builderChangedOn text update.

💡 Minimal diff
-	"builderChangedOn": "změněno v",
-	"requirementWeight": "Váha"
+	"requirementWeight": "Váha",
+	"builderChangedOn": "změněno v"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/messages/cs.json` around lines 1874 - 1875, The edit accidentally
reordered locale keys: keep the original key order by reverting the movement of
"requirementWeight" and only change the value of "builderChangedOn" to the
updated string; locate the JSON object containing "builderChangedOn" and
"requirementWeight", restore "requirementWeight" to its previous position, and
limit the hunk to just the text update for "builderChangedOn" so no other keys
are moved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@backend/iam/sso/oidc/views.py`:
- Around line 56-76: The flow must fail if a nonce was stashed but the provider
did not return a matching nonce: when you pop expected_nonce from
request.session (using _OIDC_NONCE_SESSION_PREFIX + state_id) and expected_nonce
is not None, require an id_token_str and a decoded nonce; if id_token_str is
missing throw OAuth2Error (not succeed), if decoded.get("nonce") is None throw
OAuth2Error (replace the current warning), and keep the existing error+raise
when returned_nonce != expected_nonce; use the existing helpers
_decode_id_token, get_request_param and keep provider=self.provider_id in logs
to locate the logic.

---

Nitpick comments:
In `@frontend/messages/cs.json`:
- Around line 1874-1875: The edit accidentally reordered locale keys: keep the
original key order by reverting the movement of "requirementWeight" and only
change the value of "builderChangedOn" to the updated string; locate the JSON
object containing "builderChangedOn" and "requirementWeight", restore
"requirementWeight" to its previous position, and limit the hunk to just the
text update for "builderChangedOn" so no other keys are moved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: beb4629d-5393-4d5e-bb37-fe4655e22a5b

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9e26b and 893ca33.

📒 Files selected for processing (3)
  • backend/iam/sso/oidc/views.py
  • backend/iam/sso/views.py
  • frontend/messages/cs.json

ab-smith and others added 3 commits May 28, 2026 19:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/ciso_assistant/settings.py`:
- Line 47: The _SENSITIVE_QUERY_PARAMS frozenset in settings.py is missing
"refresh_token"; update the _SENSITIVE_QUERY_PARAMS definition (the variable
named _SENSITIVE_QUERY_PARAMS) to include "refresh_token" alongside "code",
"token", "id_token", and "access_token" so refresh tokens are treated as
sensitive and redacted from logs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68a29751-3f59-4ee8-bb4b-6ea5a445eca7

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9e26b and 2541832.

📒 Files selected for processing (4)
  • backend/ciso_assistant/settings.py
  • backend/iam/sso/oidc/views.py
  • backend/iam/sso/views.py
  • frontend/messages/cs.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/iam/sso/views.py
  • frontend/messages/cs.json
  • backend/iam/sso/oidc/views.py

Comment thread backend/ciso_assistant/settings.py
@tchoumi313
Copy link
Copy Markdown
Contributor Author

@eric-intuitem LGTM

Copy link
Copy Markdown
Collaborator

@eric-intuitem eric-intuitem left a comment

Choose a reason for hiding this comment

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

LGTM

@eric-intuitem eric-intuitem merged commit e4d3a4a into main May 29, 2026
147 checks passed
@eric-intuitem eric-intuitem deleted the CA-1696-improve-oidc-security branch May 29, 2026 17:24
@github-actions github-actions Bot locked and limited conversation to collaborators May 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants