diff --git a/app/__init__.py b/app/__init__.py index 01893e4..d23cc08 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -45,7 +45,14 @@ def create_app(config_class=Config): server_metadata_url=f"{app.config['OIDC_ISSUER_URL'].rstrip('/')}/.well-known/openid-configuration", client_id=app.config["OIDC_CLIENT_ID"], client_secret=app.config["OIDC_CLIENT_SECRET"], - client_kwargs={"scope": "openid email profile"}, + # PKCE is required by the Keycloak client config (S256). Authlib + # doesn't auto-enable it from server metadata; opting in here makes + # the SDK generate the code_verifier and send code_challenge / + # code_challenge_method on the authorize redirect. + client_kwargs={ + "scope": "openid email profile", + "code_challenge_method": "S256", + }, ) from app.auth import bp as auth_bp diff --git a/app/auth/routes.py b/app/auth/routes.py index f6af083..c614c67 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -1,6 +1,7 @@ import logging from urllib.parse import urlencode +from authlib.integrations.base_client.errors import OAuthError from flask import current_app, redirect, request, session, url_for from app.auth import bp @@ -20,7 +21,20 @@ def login(): @bp.route("/callback") def callback(): - token = oauth.keycloak.authorize_access_token() + # Keycloak surfaces auth-time errors by redirecting back here with + # ?error=...&error_description=... (no `code`), which makes Authlib's + # token exchange raise OAuthError. Logging the upstream description + # makes the cause obvious in CloudWatch instead of a generic 500. + try: + token = oauth.keycloak.authorize_access_token() + except OAuthError as exc: + logger.warning( + "OIDC callback rejected by Keycloak: %s — %s", + exc.error, + exc.description, + ) + return redirect(url_for("auth.login")) + claims = token.get("userinfo") or {} sub = claims.get("sub")