Made PKCE and nonce optional, only when client wants them.

This commit is contained in:
Leo Vasanko
2026-02-16 23:55:42 +00:00
parent ce68af5be3
commit fee833b908
3 changed files with 34 additions and 39 deletions
+3 -3
View File
@@ -23,15 +23,15 @@ AUTH_CODE_LIFETIME = timedelta(seconds=60)
class OIDCCode(msgspec.Struct): class OIDCCode(msgspec.Struct):
"""An OIDC authorization code pending token exchange. """An OIDC authorization code pending token exchange.
PKCE uses S256 only (verified at auth time). PKCE uses S256 only when provided (verified at token exchange).
""" """
session_key: str session_key: str
created: datetime created: datetime
redirect_uri: str redirect_uri: str
scope: str scope: str
nonce: str nonce: str | None = None
code_challenge: str code_challenge: str | None = None
class CookieCode(msgspec.Struct): class CookieCode(msgspec.Struct):
+18 -17
View File
@@ -196,23 +196,24 @@ async def _handle_authorization_code(
status_code=400, status_code=400,
) )
# Verify PKCE (S256 only, enforced at auth time) # Verify PKCE if code_challenge was provided at authorization time
if not code_verifier: if oidc_code.code_challenge:
return JSONResponse( if not code_verifier:
{ return JSONResponse(
"error": "invalid_grant", {
"error_description": "Missing code_verifier", "error": "invalid_grant",
}, "error_description": "Missing code_verifier",
status_code=400, },
) status_code=400,
if not _verify_pkce(code_verifier, oidc_code.code_challenge): )
return JSONResponse( if not _verify_pkce(code_verifier, oidc_code.code_challenge):
{ return JSONResponse(
"error": "invalid_grant", {
"error_description": "Invalid code_verifier", "error": "invalid_grant",
}, "error_description": "Invalid code_verifier",
status_code=400, },
) status_code=400,
)
# Get user from session # Get user from session
user = db.data().users.get(session.user_uuid) user = db.data().users.get(session.user_uuid)
+13 -19
View File
@@ -158,25 +158,19 @@ async def websocket_authenticate(
await ws.send_json({"status": 400, "detail": "Scope must include openid"}) await ws.send_json({"status": 400, "detail": "Scope must include openid"})
return return
# PKCE is required with S256 # PKCE: when code_challenge is present, only S256 is supported
if not code_challenge: # If method is omitted, default to S256 per best practice (not "plain" per RFC 7636)
await ws.send_json( # When code_challenge is absent, ignore code_challenge_method entirely
{"status": 400, "detail": "PKCE code_challenge is required"} if code_challenge:
) if code_challenge_method and code_challenge_method != "S256":
return await ws.send_json(
if code_challenge_method and code_challenge_method != "S256": {
await ws.send_json( "status": 400,
{ "detail": "Only S256 code_challenge_method is supported",
"status": 400, }
"detail": "Only S256 code_challenge_method is supported", )
} return
) # Default to S256 when method not specified (implicit)
return
# Nonce is required for OIDC
if not nonce:
await ws.send_json({"status": 400, "detail": "nonce is required for OIDC"})
return
# Validate state parameter if provided (defensive against injection) # Validate state parameter if provided (defensive against injection)
if state: if state: