From c628c25081db759a3d622fe7aaf4e7b296511120 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:29:31 +0200 Subject: [PATCH] fix: handle consent_required by retrying OIDC without prompt=none MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When prompt=none fails with consent_required (scopes not yet granted), retry the OIDC flow without prompt=none so the user can grant consent once. Uses sessionStorage flag to prevent infinite loops — falls back to local login if the retry also fails. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/auth/OidcCallback.tsx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ui/src/auth/OidcCallback.tsx b/ui/src/auth/OidcCallback.tsx index c1158d07..2def73f9 100644 --- a/ui/src/auth/OidcCallback.tsx +++ b/ui/src/auth/OidcCallback.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef } from 'react'; import { Navigate, useNavigate } from 'react-router'; import { useAuthStore } from './auth-store'; +import { api } from '../api/client'; import { Card, Spinner, Alert, Button } from '@cameleer/design-system'; import { config } from '../config'; @@ -18,11 +19,31 @@ export function OidcCallback() { const errorParam = params.get('error'); if (errorParam) { - // prompt=none SSO attempt failed (no active session) — fall back to login form + // prompt=none failed — no session, fall back to login form if (errorParam === 'login_required' || errorParam === 'interaction_required') { window.location.replace(`${config.basePath}login?local`); return; } + // consent_required — retry without prompt=none so user can grant scopes + if (errorParam === 'consent_required' && !sessionStorage.getItem('oidc-consent-retry')) { + sessionStorage.setItem('oidc-consent-retry', '1'); + api.GET('/auth/oidc/config').then(({ data }) => { + if (data?.authorizationEndpoint && data?.clientId) { + const redirectUri = `${window.location.origin}${config.basePath}oidc/callback`; + const p = new URLSearchParams({ + response_type: 'code', + client_id: data.clientId, + redirect_uri: redirectUri, + scope: 'openid email profile', + }); + window.location.href = `${data.authorizationEndpoint}?${p}`; + } + }).catch(() => { + window.location.replace(`${config.basePath}login?local`); + }); + return; + } + sessionStorage.removeItem('oidc-consent-retry'); useAuthStore.setState({ error: params.get('error_description') || errorParam, loading: false, @@ -30,6 +51,8 @@ export function OidcCallback() { return; } + sessionStorage.removeItem('oidc-consent-retry'); + if (!code) { useAuthStore.setState({ error: 'No authorization code received', loading: false }); return;