OAuth2 OpenID Connect provider support etc. #3

Merged
LeoVasanko merged 65 commits from oidconnect into main 2026-02-18 02:40:27 +00:00
3 changed files with 34 additions and 39 deletions
Showing only changes of commit fee833b908 - Show all commits
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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: