mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
Implement PKCE (Proof Key for Code Exchange) for OIDC authentication to enable
support for public clients and improve OAuth security.
Changes:
- Add oauth/pkce.py with code_verifier generation and S256/plain challenge methods
- Extend OAuthService to support extra auth/token params and public clients (no client_secret)
- Implement PKCE in OIDCLoginService with code_verifier token exchange
- Store PKCE verifier in session during auth initiation (endpoints/api/user.py)
- Add get_pkce_code_verifier() helper with defensive type checking
* Encapsulates pkce_enabled check and session data extraction
* Uses isinstance(data, dict) for safe type validation
* Centralizes logic across OAuth callbacks (callback, attach, cli)
- Include example Keycloak PKCE config in local-dev/stack/config.yaml
Security improvements:
- PKCE method validation to fail fast on invalid configuration
- Defensive session data validation in OAuth callbacks
- Explicit Content-Type headers for form-encoded OAuth requests
- Optimized non-verified JWT decode (skips unnecessary key fetching)
- Exponential backoff for token exchange retries (0.5s, 1.0s, 2.0s)
Configuration:
- PKCE is opt-in via USE_PKCE config (default: disabled)
- OIDC_SERVER must end with trailing slash
- Use host.containers.internal with podman for local dev
Co-authored-by: Claude <noreply@anthropic.com>
22 lines
731 B
Python
22 lines
731 B
Python
import base64
|
|
import hashlib
|
|
import secrets
|
|
import string
|
|
|
|
_UNRESERVED = string.ascii_letters + string.digits + "-._~"
|
|
|
|
|
|
def generate_code_verifier(length: int = 64) -> str:
|
|
if length < 43 or length > 128:
|
|
raise ValueError("PKCE code_verifier length must be between 43 and 128 characters")
|
|
return "".join(secrets.choice(_UNRESERVED) for _ in range(length))
|
|
|
|
|
|
def code_challenge(verifier: str, method: str = "S256") -> str:
|
|
if method.upper() == "PLAIN":
|
|
return verifier
|
|
if method.upper() != "S256":
|
|
raise ValueError("Unsupported PKCE method: %s" % method)
|
|
digest = hashlib.sha256(verifier.encode("ascii")).digest()
|
|
return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
|