Auth / FaceID — eingeloggt bleiben funktioniert jetzt: - AppLock-Init idempotent: late re-init durch router.replace-Re-Mount behält locked-State (fixt Endlosschleife: unlock → re-mount → init reset → lock) - LockScreen-Auto-Prompt nur wenn AppState=active (verhindert silent FaceID- Fail wenn LockScreen während background-Event mountet — User sah dann nur Fallback-Button) - index.tsx: wenn Session schon in AsyncStorage liegt → router.replace zu /(app), Landing wird übersprungen; early-return nach allen Hooks (Rules of Hooks) - WebBrowser.dismissAuthSession vor openAuthSessionAsync (verhindert "Another web browser is already open" nach abgebrochenen OAuth-Flows) UI — iOS-Grouped-Look auf Settings + Profile: - Neue Theme-Tokens groupedBg (#F2F2F7 / #000) + card (#fff / #1c1c1e), identisch zu Apples systemGroupedBackground / secondarySystemGroupedBackground - settings.tsx + profile/index.tsx + profile/[userId].tsx: Page-BG → groupedBg - StreakSection / UrgeStatsCard / DemographicsAccordion / StatsBar / ApprovedDomainsList: Card-BG colors.surface → colors.card Mail-Connect — Outlook-Tile entschärft: - Microsoft hat App-Passwords für consumer-Outlook (.com/hotmail/live/msn) im September 2024 abgeschaltet, der bisherige Guide-Flow ist seit ~8 Monaten wirkungslos → AUTHENTICATIONFAILED - Tile bleibt sichtbar mit opacity 0.45, "Kommt bald"-Sub-Label, disabled=true - Provider-Typ um disabled? + disabledLabelKey? erweitert (wiederverwendbar) - Backend-OAuth-Plan unter backend/docs/mail-outlook-oauth-plan.md (mo) → Generisches AuthMethod-Framework (app_password | oauth) geplant Profile — Cooldown-Verlauf als Sparkline statt Endlos-Liste: - 8 Wochen-Buckets, Bar-Höhe nach Frequenz (cap 5/Woche), leere Wochen als 2px-Flatlines - Sub-Label: "{n} Cooldowns in 8 Wochen · Ø 1 pro {avg} Wochen · zuletzt {date}" - Neutral formuliert (Sucht-/Stigma-Sensibilität: Cooldown = Schutz-Pause, kein Rückfall) - useProfileData.ts liefert rawStartedAt (ISO) zusätzlich zum formatierten Wert - i18n-Keys unter profile.cooldown.* in DE + EN Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 KiB
Outlook OAuth2 — Implementierungsplan
Stand: 2026-05-13
Autor: Mo (Mail-Stack-Owner)
Status: Plan, kein Code
1. Status-Recherche: Microsoft Basic-Auth-Deprecation
Was ist passiert
Microsoft hat Basic-Auth (username + password) für consumer-Outlook-Mailboxen (outlook.com, hotmail.com, hotmail.de, live.com, live.de, msn.com) schrittweise abgeschaltet:
- September 2024: Vollständige Abschaltung für neue IMAP/POP/SMTP-Verbindungen mit Basic-Auth auf consumer-Tenants. Bestehende Verbindungen hatten eine Übergangsfrist.
- Stand Mai 2026: Basic-Auth ist für alle consumer-Outlook-Postfächer tot.
IMAP-Login mit Passwort schlägt mit
[AUTHENTICATIONFAILED]fehl — egal ob App-Passwort oder normales Passwort.
Edge-Cases
| Szenario | Basic-Auth möglich? |
|---|---|
| outlook.com / hotmail / live / msn — consumer | Nein, komplett tot |
| Microsoft 365 Business (firmeneigene Domain, Azure-AD-Tenant) | Nein, Admins können es nicht reaktivieren |
| Outlook.com custom domain (eigene Domain via Outlook-Webmail) | Nein, gleiche Infrastruktur |
| On-Premise Exchange (eigener Firmen-Server) | Hypothetisch ja, aber nicht unser Use-Case |
Fazit: Es gibt keinen Edge-Case der uns rettet. Der App-Passwort-Guide im
ConnectMailSheet ist für Outlook-User seit September 2024 nutzlos. Jeder
Outlook-User der jetzt "Verbinden" drückt bekommt vom Backend
AUTHENTICATIONFAILED zurück.
Benoetigte OAuth-Scopes
Fuer IMAP read + delete via XOAUTH2 gegen Microsoft Identity Platform:
https://outlook.office.com/IMAP.AccessAsUser.All
offline_access
openid
IMAP.AccessAsUser.All— erlaubt IMAP-Zugriff im Namen des Users (lesen, loeschen, verschieben). Kein weiterer Mail-Scope noetig.offline_access— liefert einen refresh_token (ohne ihn gibt es keinen refresh_token, nur kurze access_tokens). Pflicht fuer langlebige IDLE-Sessions.openid— liefert sub/email im ID-Token fuer Account-Identifikation.
Explizit NICHT anfordern: Mail.Read, Mail.ReadWrite, Contacts.*,
Calendars.*, User.Read (ausser sub/email). Minimale Scope-Anforderung.
Consumer Identity Platform vs Azure-AD
Microsoft hat zwei Systeme:
- Microsoft Identity Platform v2 (consumers) — fuer outlook.com/hotmail-Privat-
konten. Endpoint:
https://login.microsoftonline.com/consumers/oauth2/v2.0/...oder tenant-agnostischcommon. Azure-App-Registrierung mit "Supported account types: Personal Microsoft accounts only" oder "Any Microsoft account (multi-tenant- personal)".
- Azure-AD / Entra ID (work/school) — fuer M365-Business. Nicht unser primaerer Use-Case.
Fuer Rebreak: App-Registrierung mit consumers-Endpoint — deckt alle genannten
Domains ab (outlook.com, hotmail, live, msn). Wer ein M365-Business-Konto hat,
faellt spaeter unter den gleichen Flow wenn wir auf common wechseln.
2. Architektur-Plan
2.1 Azure-App-Registrierung
Einmaliges Setup im Azure-Portal (portal.azure.com):
| Feld | Wert |
|---|---|
| Name | Rebreak Mail Access |
| Supported account types | Personal Microsoft accounts only |
| Redirect URI (Mobile) | msauth.org.rebreak.app://auth (MSAL-Schema) |
| Redirect URI (Web/BFF) | https://api.rebreak.org/api/mail/oauth/microsoft/callback |
| API Permissions | IMAP.AccessAsUser.All (delegated), offline_access, openid |
| Client secret | Ja (fuer BFF-Token-Exchange) |
| Public client flows | Ja aktivieren (fuer PKCE) |
Scopes muessen im Portal unter "API Permissions" explizit hinzugefuegt und fuer
consumers-Tenant fuer alle User freigegeben werden. Kein "Grant admin consent"
noetig fuer delegated permissions auf consumer-Tenant.
Multi-Tenant-Approval erforderlich? Nein. Bei "Personal Microsoft accounts
only" gibt es keinen App-Review-Prozess bei Microsoft — jeder MS-User kann der
App konsentieren. App-Reviews sind nur noetig wenn man All organizations-Tenant
anfordert und enterprise-Features braucht.
2.2 OAuth-Flow: BFF-Pattern (Backend-mediated)
Empfehlung: BFF-Pattern, nicht PKCE direkt im Mobile-Client.
Begruendung:
- Client-secret darf nicht im App-Bundle liegen (App-Store-Guidelines, Reverse- Engineering). PKCE ohne client_secret ist moeglich aber dann kein refresh_token via MSAL fuer native — Microsoft erlaubt es fuer public clients, aber Token- Rotation ist dann Clients-Sache.
- Wir haben bereits den BFF-Ansatz beim Auth-Login. Konsistenz.
- Token-Storage (encrypted, server-side) ist ohnehin Backend-Aufgabe.
Flow-Sequenz:
Native App Backend Microsoft
| | |
| GET /api/mail/oauth/ | |
| microsoft/authorize | |
| (mit state+code_challenge) | |
|---------------------------->| |
| | build auth URL |
| 302 redirect URL | (PKCE, state, scopes) |
|<----------------------------| |
| | |
| WebBrowser.openAuthSession | |
| oeffnet MS-Login | |
|-------------------------------------------->| |
| | | User loggt |
| | | ein, |
| | | konsentiert |
|<--------------------------------------------| |
| redirect: .../callback?code=XXX&state=YYY | |
| | |
| POST /api/mail/oauth/ | |
| microsoft/exchange | |
| body: { code, state } | |
|---------------------------->| |
| | POST token endpoint |
| | (code + code_verifier) |
| |---------------------------->|
| |<----------------------------|
| | { access_token, |
| | refresh_token, |
| | expires_in } |
| | |
| | decrypt+store tokens |
| | upsert MailConnection |
| | |
| { connected: true } | |
|<----------------------------| |
Zwei neue Backend-Endpoints:
GET /api/mail/oauth/microsoft/authorize— generiert state + PKCE-Verifier, speichert state temporaer in DB/Session, gibt redirect URL zurueckPOST /api/mail/oauth/microsoft/exchange— empfaengt code + state, tauscht gegen tokens, speichert in MailConnection
2.3 Token-Storage: Schema-Aenderung (Eskalation an rebreak-backend)
ESKALATION AN rebreak-backend erforderlich.
Das aktuelle MailConnection-Schema hat passwordEncrypted: String. Fuer OAuth
brauchen wir:
// Neue Felder in MailConnection:
authMethod String @default("password") @map("auth_method")
// "password" | "oauth2_microsoft" | "oauth2_google" (future)
oauthAccessToken String? @map("oauth_access_token") // AES-256-GCM encrypted
oauthRefreshToken String? @map("oauth_refresh_token") // AES-256-GCM encrypted
oauthTokenExpiry DateTime? @map("oauth_token_expiry") // UTC, naechste Ablaufzeit
oauthScope String? @map("oauth_scope") // gespeicherter Scope-String
passwordEncrypted bleibt fuer bestehende password-basierte Connections.
Fuer OAuth-Connections: passwordEncrypted = leer string oder "oauth" als
Marker, damit bestehender Code nicht bricht. Besser: authMethod-Flag pruefe
zuerst.
Schema-Migration: ALTER TABLE rebreak.mail_connections ADD COLUMN ... (4 neue
Spalten). Kein Breaking Change fuer bestehende Rows.
2.4 IMAP-Connect-Logik: XOAUTH2 in ImapFlow
Gute Nachricht: imapflow (aktuell ^1.2.18) unterstuetzt XOAUTH2 nativ.
Aktueller Auth-Block in connect.post.ts und imap-idle/index.mjs:
auth: { user: email, pass: password }
Fuer OAuth: ImapFlow akzeptiert stattdessen:
auth: {
user: email,
accessToken: decryptedAccessToken
}
ImapFlow baut daraus automatisch den XOAUTH2-SASL-String. Kein manueller Base64-Encoding noetig, keine Library-Aenderung erforderlich.
imap-providers.ts braucht ein neues Interface:
export interface ImapAuth {
type: 'password' | 'oauth2';
value: string; // password (plaintext, decrypted) ODER access_token
}
Die Resolve-Logik in connect.post.ts muss authMethod aus MailConnection
lesen und die richtige ImapAuth zusammenbauen.
2.5 Token-Refresh-Flow
Das haerteste Problem. Access-tokens laufen bei Microsoft nach 1 Stunde ab.
Betroffen sind zwei Stellen:
A. IMAP-Idle-Daemon (langlebige Verbindung, laeuft tage-/wochenlang):
Der Daemon muss vor jedem connect (und nach AUTHENTICATIONFAILED-Fehlern) pruefen ob der access_token noch gueltig ist. Refresh-Logik:
1. oauthTokenExpiry aus DB lesen
2. Wenn expiry < now + 5min:
a. POST https://login.microsoftonline.com/consumers/oauth2/v2.0/token
mit: grant_type=refresh_token, refresh_token=<decrypted>, client_id, client_secret
b. Neues access_token + refresh_token in DB speichern (encrypted)
c. oauthTokenExpiry updaten
3. ImapFlow mit frischem access_token verbinden
Refresh im Daemon direkt (kein HTTP-Roundtrip zum Backend noetig — Daemon hat direkten DB-Zugriff). Der Daemon erhaelt client_id + client_secret als Env-Vars.
Token-Rotation: Microsoft kann bei refresh auch ein neues refresh_token liefern ("refresh token rotation"). Daemon muss das neue refresh_token persistieren, sonst ist nach einem Refresh der naechste fehlgeschlagen.
B. scan.post.ts / connect.post.ts (kurze Connections):
Beim On-Demand-Scan: pruefe oauthTokenExpiry und refresh wenn noetig, bevor
IMAP-Connection aufgebaut wird. Da dieser Code im Nitro-Kontext laeuft, kann
er direkt Prisma nutzen.
Refresh-Token-Revocation bei User-Logout / Account-Loeschung: Backend muss
POST https://login.microsoftonline.com/consumers/oauth2/v2.0/logout aufrufen
wenn User die Verbindung trennt oder Account loescht. Sonst bleibt unsere App-
Autorisierung bei Microsoft aktiv.
3. ConnectMailSheet UX-Plan (fuer rebreak-native-ui-Agent)
Geaenderter Flow fuer Outlook
Aktuell: Outlook-Provider-Tile -> Formular mit Email + App-Passwort-Hinweis + Link.
Neu: Outlook-Provider-Tile -> Anderer View (kein Passwort-Formular):
[Tile: Outlook / Hotmail / Live]
|
v
View: "outlook-oauth"
+---------------------------------+
| [Outlook-Icon] |
| Mit Microsoft anmelden |
| |
| Rebreak benoetigt Zugriff auf |
| dein Postfach um Gluecksspiel- |
| Mails automatisch zu loeschen. |
| |
| [Schild-Icon] Datenschutz: |
| Wir lesen keine Inhalte. Nur |
| Absender + Betreff zum Matching|
| |
| [Button] Mit Microsoft anmelden|
| |
| [Spinner waehrend OAuth laeuft]|
+---------------------------------+
States:
- idle: Button aktiv, Datenschutz-Hinweis sichtbar
- loading: Button disabled, ActivityIndicator, Text "Verbindung wird hergestellt..."
- error: Roter Error-Text unter Button (z.B. "Zugriff verweigert" wenn User Consent ablehnt, oder "Verbindung fehlgeschlagen" bei Network-Error)
- success: Sheet schliesst sich, onSuccess() wird aufgerufen
Technisch im Client:
1. Button-Tap → GET /api/mail/oauth/microsoft/authorize
2. Backend gibt { authUrl: "https://login.microsoftonline.com/..." } zurueck
3. expo-web-browser: WebBrowser.openAuthSessionAsync(authUrl, redirectUri)
4. Deep-Link-Handler empfaengt Callback-URL mit code + state
5. POST /api/mail/oauth/microsoft/exchange mit { code, state }
6. On success: handleClose() + onSuccess()
Redirect-URI in der App: msauth.org.rebreak.app://auth — muss in
app.json-Scheme registriert und in Azure-App-Registrierung eingetragen sein.
Bestehende Provider unveraendert: Gmail, iCloud, Yahoo, GMX, Other behalten den Passwort-Formular-Flow. Nur Outlook-Tile bekommt anderen View.
4. DSGVO-/Compliance-Aspekte (fuer Hans-Mueller-DSB-Review)
ESKALATION AN hans-mueller fuer formelles Review.
4.1 Microsoft als Sub-Auftragsverarbeiter
Microsoft wird durch den OAuth-Flow zusaetzlicher Sub-AV (Art. 28 DSGVO). Microsoft hat ein Standard-DPA das automatisch gilt wenn man Azure-Services nutzt (Microsoft Products and Services Data Protection Addendum — DPA). Zu pruefen:
- Gilt das DPA auch fuer consumer Microsoft Identity Platform?
- Muss in unserem AV-Vertraege-Verzeichnis (VVT) erwaehnt werden?
- Microsoft hat EU-Datenzentren — Transfer-Grundlage sollte Standard-Vertragsklauseln oder Adequacy-Decision sein.
4.2 Token-Speicherung = sensibler als Passwort
Ein refresh_token gibt persistenten Zugriff auf das Postfach bis zur Revocation — laenger als ein App-Passwort (das der User jederzeit in Sekunden zurueckziehen kann). Konsequenzen:
- Verschluesselung at-rest: gleicher AES-256-GCM wie bei
passwordEncrypted. Gleicher ENCRYPTION_KEY. Kein anderer Speicherweg. - Zugriff auf refresh_token = Zugriff auf gesamtes Postfach. Breach-Impact hoeher als bei App-Passwort.
- Im Datenschutzhinweis in der App und in der Datenschutzerklaerung explizit erwaehnen: "Wir speichern einen Zugriffstoken der im Namen des Users auf das Postfach zugreift".
4.3 Datenminimierung
Scopes beschraenken auf:
IMAP.AccessAsUser.All— Minimum fuer IMAPoffline_access— Minimum fuer Token-Refreshopenid— fuer Email-Identifikation (keinprofile-Scope)
Kein User.Read.All, kein Contacts.*, kein Calendars.*.
4.4 Loeschpflicht / Widerrufs-Pflicht
Bei User-Disconnect oder Account-Loeschung:
- refresh_token + access_token aus DB loeschen
- Token bei Microsoft revoken via:
POST https://login.microsoftonline.com/consumers/oauth2/v2.0/token/revoke(mit refresh_token als Parameter)
Ohne Revocation bleibt Rebreaks App-Autorisierung bei Microsoft aktiv — auch wenn wir die DB-Eintraege loeschen.
4.5 Speicherort
Token in MailConnection-Tabelle, gleicher Postgres-Host wie alle anderen User-
daten. Kein separater Secret-Store noetig wenn AES-256-GCM konsistent angewandt
wird.
5. Aufwands-Schaetzung
MVP-Scope
MVP = OAuth-Login funktioniert, User kann Outlook verbinden, IMAP-IDLE loescht Gambling-Mails, Token-Refresh laeuft automatisch.
| Komponente | Aufwand |
|---|---|
| Azure-App-Registrierung (einmaliges Setup) | 0.5 Tage |
| Schema-Migration (4 neue Spalten, rebreak-backend) | 0.5 Tage |
| Backend: 2 neue Endpoints (authorize + exchange) | 1.5 Tage |
| connect.post.ts: authMethod-Logik + XOAUTH2-Support | 0.5 Tage |
| imap-idle: Token-Refresh-Logik + XOAUTH2-Auth | 1.5 Tage |
| scan.post.ts: Token-Refresh vor on-demand-scan | 0.5 Tage |
| disconnect.delete.ts: Token-Revocation bei MS | 0.5 Tage |
| ConnectMailSheet: Outlook-OAuth-View (native-ui-agent) | 1.0 Tag |
| Deep-Link-Handling in App + expo-web-browser Setup | 0.5 Tage |
| Testen end-to-end (inkl. Token-Refresh-Simulation) | 1.0 Tag |
| Gesamt | ~8 Personentage |
Risiken
1. Microsoft Rate Limits auf Free-Tier Azure-App
Azure-Apps haben per default Rate-Limits auf den Token-Endpoint. Bei vielen
Usern gleichzeitig (Token-Refresh alle ~55min pro User) koennte das ein Problem
werden. Grenzwert: 30 Requests/Sekunde per App fuer /token-Endpoint.
Bei 1000 aktiven Outlook-Usern: ~18 Refreshes/Minute → kein Problem. Bei 10.000
Users: Grenzwert naeherungsweise erreicht. Fruehzeitig Azure-Subscription-Limit
pruefen.
2. Token-Rotation race condition im IDLE-Daemon
Wenn mehrere IDLE-Sessions parallel starten (z.B. nach Daemon-Restart) und alle gleichzeitig ein abgelaufenes Token refreshen wollen, koennen race conditions entstehen: doppelter Refresh → alter refresh_token ungueltig → zweite Session failt. Loesung: DB-Lock oder last-writer-wins mit Timestamp-Check.
3. Consumer-Tenant Consent-Screen
Beim ersten OAuth-Login sieht der User den Microsoft-Consent-Screen mit der Formulierung "Rebreak moechte auf dein Postfach zugreifen". Fuer manche User (besonders aengstliche) koennte das abschreckend wirken. Das ist kein technisches Risiko aber ein UX-Risiko — der Datenschutz-Hinweis im Sheet muss das vorab erklaeren.
4. App-Registrierung: Publisher-Verification
Microsoft kann nicht-verifizierte Publisher-Apps auf dem Consent-Screen als "unverified" markieren. Fuer Produktivbetrieb sollte Publisher-Verification in Azure abgeschlossen werden (Domain-Verifikation von rebreak.org). Aufwand: ~1 Tag einmalig. Ohne Verifikation funktioniert der Flow trotzdem, aber der Consent- Screen zeigt "unverified publisher" — schlechtes Vertrauen.
5. Apple App-Store-Review: OAuth-Flows
OAuth-Flows in iOS-Apps koennen zu App-Store-Review-Verzoegerungen fuehren wenn der Reviewer nicht einen echten Microsoft-Account zum Testen hat. Testaccount fuer Review bereitstellen (outlook.com-Testaccount mit Gambling-Mails).
6. Kein App-Review bei Microsoft selbst erforderlich
"Personal Microsoft accounts only"-Apps brauchen keine Microsoft-seitige Freigabe. Kein Warten auf MS-Approval.
6. Abhaengigkeiten und naechste Schritte
Sofortige Eskalationen
-
rebreak-backend: Schema-Migration fuer 4 neue Felder in
MailConnection. Neue Felder:auth_method,oauth_access_token,oauth_refresh_token,oauth_token_expiry. Migration kann non-destructive (additive) sein. -
hans-mueller: DSGVO-Review der Token-Speicherung (Abschnitt 4). Insbesondere: Microsoft als Sub-AV ins VVT aufnehmen, Datenschutzerklaerung anpassen (refresh_token = persistenter Zugriff), Revocations-Pflicht bei Loeschung.
Entscheidung vor Implementierungsstart
- Azure-Account + App-Registrierung: wer legt an? (ops-Aufgabe)
- client_id + client_secret: werden via Infisical verwaltet (klar), aber Infisical-Secret-Naming vorab festlegen.
- Redirect-URI-Schema (
msauth.org.rebreak.app): muss inapp.jsonregistriert sein bevor iOS-Build fuer Tests.
Kein Handlungsbedarf bis Schema-Migration done
Die Backend-Endpoints koennen erst nach dem Schema-Change implementiert werden. Warten auf rebreak-backend, dann direkt loslegen.
7. Was wir heute sofort tun koennen (ohne Schema-Change)
Unabhaengig vom OAuth-Implementierungs-Timeline:
-
ConnectMailSheet: Outlook-Tile sofort deaktivieren oder Hinweis einblenden "Outlook wird bald unterstuetzt". Besser als den User einen Fehler erleben lassen ("AUTHENTICATIONFAILED" nach Eingabe eines App-Passworts das sowieso nicht funktioniert). Das ist eine UI-Aenderung fuer native-ui-agent.
-
imap-providers.ts:
isOAuthRequired-Flag fuer Outlook-Domains vorbereiten, damit connect.post.ts frueizeitig auf "oauth not yet implemented" antworten kann statt mit generischem Auth-Fehler zu failen.
Diese zwei Punkte koennen vor der Schema-Migration deployed werden.