Microsoft-Login auf Redirect statt Popup umgestellt (Handy/Tablet-Fix)
Build and Deploy / version (push) Successful in 1s
Build and Deploy / build (push) Successful in 9s
Build and Deploy / deploy (push) Successful in 1s

Der Popup-basierte Login/Logout und die stille Token-Erneuerung per
Hintergrund-iFrame sind in installierten PWAs auf iOS/Android unzuverlässig -
Popups werden blockiert oder verlieren die Verbindung zum öffnenden Fenster,
und iOS' ITP blockiert oft den iFrame-Speicherzugriff für die stille
Erneuerung. Das zwang Monteure am Handy/Tablet zu ständigen Neuanmeldungen,
während es am PC funktionierte. Login/Logout laufen jetzt über einen vollen
Redirect, scheiternde stille Token-Erneuerung fällt sauber auf einen Redirect
zurück statt mit einem Fehler abzubrechen, und storeAuthStateInCookie ist
aktiviert (Microsofts eigene Empfehlung für Safari/iOS).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fidelis Huber
2026-09-30 23:02:09 +02:00
co-authored by Claude Sonnet 5
parent b591991d19
commit 3a82a22135
5 changed files with 33 additions and 7 deletions
+5 -1
View File
@@ -18,7 +18,11 @@ export default function Header({ localUser, onLocalLogout }: { localUser: string
const [themeMode, setThemeMode] = useState<ThemeMode>(getStoredTheme());
function logout() {
if (account) instance.logoutPopup();
if (account) {
// Redirect statt Popup, aus demselben Grund wie beim Login (siehe Login.tsx).
instance.logoutRedirect();
return;
}
onLocalLogout();
}
+11 -2
View File
@@ -9,8 +9,17 @@ async function authHeader(): Promise<HeadersInit> {
}
const account = msalInstance.getAllAccounts()[0];
if (!account) throw new Error('Nicht angemeldet.');
const result = await msalInstance.acquireTokenSilent({ scopes: ['openid', 'profile'], account });
return { Authorization: `Bearer ${result.idToken}`, 'Content-Type': 'application/json' };
try {
const result = await msalInstance.acquireTokenSilent({ scopes: ['openid', 'profile'], account });
return { Authorization: `Bearer ${result.idToken}`, 'Content-Type': 'application/json' };
} catch {
// Silent Renewal (per Hintergrund-iFrame) scheitert auf iOS/Safari häufig, weil ITP
// Drittanbieter-Speicherzugriff im iFrame blockiert. Statt mit einem kryptischen
// Fehler abzubrechen, sauber per Redirect neu anmelden – der Nutzer landet danach
// wieder genau hier.
await msalInstance.acquireTokenRedirect({ scopes: ['openid', 'profile'], account });
throw new Error('Anmeldung wird erneuert …');
}
}
async function req<T>(url: string, init?: RequestInit): Promise<T> {
+5 -1
View File
@@ -14,7 +14,11 @@ const msalConfig: Configuration = {
cache: {
// localStorage statt sessionStorage: bleibt erhalten, wenn die (installierte) App
// geschlossen und neu gestartet wird – sonst müsste man sich jedes Mal neu anmelden.
cacheLocation: 'localStorage'
cacheLocation: 'localStorage',
// Von Microsoft explizit für Safari/iOS empfohlen: hält den Auth-State zusätzlich
// in einem Cookie, da Safaris ITP die interne Silent-Renewal-/Redirect-Logik sonst
// gerne stört – genau das Symptom "am Handy ständig neu anmelden müssen".
storeAuthStateInCookie: true
}
};
+7 -1
View File
@@ -8,7 +8,13 @@ import './styles.css';
initTheme();
msalInstance.initialize().then(() => {
msalInstance.initialize().then(() =>
// Verarbeitet die Rückkehr von loginRedirect() (Popups sind in installierten PWAs auf
// Handys/Tablets unzuverlässig – deshalb nutzt der Microsoft-Login einen vollen
// Redirect statt eines Popups, siehe Login.tsx).
msalInstance.handleRedirectPromise()
).then(result => {
if (result?.account) msalInstance.setActiveAccount(result.account);
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<MsalProvider instance={msalInstance}>
+5 -2
View File
@@ -14,11 +14,14 @@ export default function Login({ onLocalLogin }: { onLocalLogin: (name: string) =
setBusy(true);
setErr('');
try {
await instance.loginPopup({ scopes: ['openid', 'profile'] });
// Redirect statt Popup: Popups werden in installierten PWAs auf Handys/Tablets
// (Homescreen-App) von iOS/Android oft blockiert oder verlieren die Verbindung
// zum öffnenden Fenster – dadurch musste man sich dort ständig neu anmelden.
await instance.loginRedirect({ scopes: ['openid', 'profile'] });
} catch (e) {
setErr(e instanceof Error ? e.message : String(e));
setBusy(false);
}
setBusy(false);
}
async function submitLocal(e: FormEvent) {