Source code for kelvin.sdk.services.auth.pkce
"""PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0."""
from __future__ import annotations
import base64
import hashlib
import secrets
from urllib.parse import urlencode
[docs]
def generate_code_verifier() -> str:
"""Generate a cryptographically random code verifier for PKCE.
Returns:
A URL-safe base64 encoded random string (43 characters).
"""
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode("utf-8")
return code_verifier.rstrip("=")
[docs]
def generate_code_challenge(code_verifier: str) -> str:
"""Generate a code challenge from a code verifier for PKCE.
Args:
code_verifier: The code verifier string.
Returns:
URL-safe base64 encoded SHA256 hash of the verifier.
"""
digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(digest).decode("utf-8")
return code_challenge.rstrip("=")
[docs]
def generate_state() -> str:
"""Generate a random state parameter for CSRF protection.
Returns:
A random URL-safe string.
"""
return secrets.token_urlsafe(32)
[docs]
def build_authorization_url(
authorization_endpoint: str,
client_id: str,
redirect_uri: str,
code_challenge: str,
state: str,
scope: str = "openid profile email",
) -> str:
"""Build the OAuth authorization URL with PKCE parameters.
Args:
authorization_endpoint: The Keycloak authorization endpoint.
client_id: The OAuth client ID.
redirect_uri: The redirect URI (callback URL).
code_challenge: The PKCE code challenge.
state: The state parameter for CSRF protection.
scope: The OAuth scopes to request.
Returns:
The complete authorization URL.
"""
params = {
"response_type": "code",
"client_id": client_id,
"redirect_uri": redirect_uri,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
"state": state,
"scope": scope,
}
return f"{authorization_endpoint}?{urlencode(params)}"