# 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-agnostisch `common`. 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 zurueck - `POST /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: ```prisma // 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`: ```js auth: { user: email, pass: password } ``` Fuer OAuth: ImapFlow akzeptiert stattdessen: ```js 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: ```ts 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=, 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 IMAP - `offline_access` — Minimum fuer Token-Refresh - `openid` — fuer Email-Identifikation (kein `profile`-Scope) Kein `User.Read.All`, kein `Contacts.*`, kein `Calendars.*`. ### 4.4 Loeschpflicht / Widerrufs-Pflicht Bei User-Disconnect oder Account-Loeschung: 1. refresh_token + access_token aus DB loeschen 2. 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 1. **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. 2. **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 in `app.json` registriert 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: 1. **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. 2. **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.