OAuth2 OpenID Connect provider support etc. #3
+3
-3
@@ -23,15 +23,15 @@ AUTH_CODE_LIFETIME = timedelta(seconds=60)
|
||||
class OIDCCode(msgspec.Struct):
|
||||
"""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
|
||||
created: datetime
|
||||
redirect_uri: str
|
||||
scope: str
|
||||
nonce: str
|
||||
code_challenge: str
|
||||
nonce: str | None = None
|
||||
code_challenge: str | None = None
|
||||
|
||||
|
||||
class CookieCode(msgspec.Struct):
|
||||
|
||||
+18
-17
@@ -196,23 +196,24 @@ async def _handle_authorization_code(
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Verify PKCE (S256 only, enforced at auth time)
|
||||
if not code_verifier:
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_description": "Missing code_verifier",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
if not _verify_pkce(code_verifier, oidc_code.code_challenge):
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_description": "Invalid code_verifier",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
# Verify PKCE if code_challenge was provided at authorization time
|
||||
if oidc_code.code_challenge:
|
||||
if not code_verifier:
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_description": "Missing code_verifier",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
if not _verify_pkce(code_verifier, oidc_code.code_challenge):
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_description": "Invalid code_verifier",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Get user from session
|
||||
user = db.data().users.get(session.user_uuid)
|
||||
|
||||
+13
-19
@@ -158,25 +158,19 @@ async def websocket_authenticate(
|
||||
await ws.send_json({"status": 400, "detail": "Scope must include openid"})
|
||||
return
|
||||
|
||||
# PKCE is required with S256
|
||||
if not code_challenge:
|
||||
await ws.send_json(
|
||||
{"status": 400, "detail": "PKCE code_challenge is required"}
|
||||
)
|
||||
return
|
||||
if code_challenge_method and code_challenge_method != "S256":
|
||||
await ws.send_json(
|
||||
{
|
||||
"status": 400,
|
||||
"detail": "Only S256 code_challenge_method is supported",
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
# Nonce is required for OIDC
|
||||
if not nonce:
|
||||
await ws.send_json({"status": 400, "detail": "nonce is required for OIDC"})
|
||||
return
|
||||
# PKCE: when code_challenge is present, only S256 is supported
|
||||
# If method is omitted, default to S256 per best practice (not "plain" per RFC 7636)
|
||||
# When code_challenge is absent, ignore code_challenge_method entirely
|
||||
if code_challenge:
|
||||
if code_challenge_method and code_challenge_method != "S256":
|
||||
await ws.send_json(
|
||||
{
|
||||
"status": 400,
|
||||
"detail": "Only S256 code_challenge_method is supported",
|
||||
}
|
||||
)
|
||||
return
|
||||
# Default to S256 when method not specified (implicit)
|
||||
|
||||
# Validate state parameter if provided (defensive against injection)
|
||||
if state:
|
||||
|
||||
Reference in New Issue
Block a user