fix: auto-redirect to Keycloak login, skip intermediate screen

Since tOS uses Keycloak as sole auth provider, the intermediate
login page with the manual "Mit Keycloak anmelden" button was
unnecessary. Now unauthenticated users are redirected directly
to Keycloak. The error UI with retry button is preserved for
failed auth attempts (expired session, unauthorized).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 13:02:49 +01:00
parent 20d6795694
commit 35cc24c5ec

View File

@@ -3,7 +3,7 @@
import { signIn, useSession } from 'next-auth/react'; import { signIn, useSession } from 'next-auth/react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Loader2, KeyRound } from 'lucide-react'; import { Loader2, KeyRound } from 'lucide-react';
@@ -16,7 +16,7 @@ interface LoginPageProps {
/** /**
* Login page component * Login page component
* Handles authentication via Keycloak * Automatically redirects to Keycloak. Only shows UI on auth errors.
*/ */
export default function LoginPage({ params: { locale } }: LoginPageProps) { export default function LoginPage({ params: { locale } }: LoginPageProps) {
const { status } = useSession(); const { status } = useSession();
@@ -24,6 +24,7 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const t = useTranslations('auth'); const t = useTranslations('auth');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const autoSignInTriggered = useRef(false);
const callbackUrl = searchParams.get('callbackUrl') || `/${locale}/dashboard`; const callbackUrl = searchParams.get('callbackUrl') || `/${locale}/dashboard`;
const error = searchParams.get('error'); const error = searchParams.get('error');
@@ -35,6 +36,14 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
} }
}, [status, router, callbackUrl]); }, [status, router, callbackUrl]);
// Auto-redirect to Keycloak if no error
useEffect(() => {
if (status === 'unauthenticated' && !error && !autoSignInTriggered.current) {
autoSignInTriggered.current = true;
signIn('keycloak', { callbackUrl });
}
}, [status, error, callbackUrl]);
const handleLogin = async () => { const handleLogin = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
@@ -44,8 +53,8 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
} }
}; };
// Show loading state while checking session // Show loading spinner during auto-redirect or session check
if (status === 'loading') { if (!error) {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background"> <div className="flex min-h-screen items-center justify-center bg-background">
<Loader2 className="h-8 w-8 animate-spin text-primary" /> <Loader2 className="h-8 w-8 animate-spin text-primary" />
@@ -53,6 +62,7 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
); );
} }
// Only show full UI when there's an error (so user can retry)
return ( return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-background to-muted p-4"> <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-background to-muted p-4">
<motion.div <motion.div
@@ -62,7 +72,6 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
> >
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader className="space-y-4 text-center"> <CardHeader className="space-y-4 text-center">
{/* Logo */}
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground"> <div className="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
<span className="text-3xl font-bold">t</span> <span className="text-3xl font-bold">t</span>
<span className="text-3xl font-light">OS</span> <span className="text-3xl font-light">OS</span>
@@ -73,18 +82,14 @@ export default function LoginPage({ params: { locale } }: LoginPageProps) {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{/* Error message */} <motion.div
{error && ( initial={{ opacity: 0, height: 0 }}
<motion.div animate={{ opacity: 1, height: 'auto' }}
initial={{ opacity: 0, height: 0 }} className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive"
animate={{ opacity: 1, height: 'auto' }} >
className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive" {error === 'SessionRequired' ? t('sessionExpired') : t('unauthorized')}
> </motion.div>
{error === 'SessionRequired' ? t('sessionExpired') : t('unauthorized')}
</motion.div>
)}
{/* Login button */}
<Button <Button
onClick={handleLogin} onClick={handleLogin}
disabled={isLoading} disabled={isLoading}