"Sign in with Microsoft" (Outlook) crashed on the redirect back from the browser and never completed a login. This fixes three layered bugs plus some hardening in the account-setup flow. Verified end-to-end on a real Outlook account on a physical device (redirect → token exchange → account added → inbox sync).
Root causes & fixes (in flow order)
Redirect crash (the reported symptom) — AppAuth's RedirectUriReceiverActivity extends AppCompatActivity, so it needs a Theme.AppCompat theme. This app is pure Compose (MainActivity : ComponentActivity, framework Theme.Material), and AppAuth declares that activity with no theme of its own, so it inherited the Material app theme and threw "You need to use a Theme.AppCompat theme" the instant Android launched it to deliver the redirect. Fix: give it the translucent AppCompat theme AppAuth itself applies to AuthorizationManagementActivity. Build-type independent — not an R8 issue.
Token exchange rejected with AADSTS70011 ("must include a 'scope' input parameter") — one consent spans two Microsoft resources (Graph for send, Exchange Online for IMAP), so Microsoft mints one token per resource and the code→token exchange must name a single resource. AppAuth's createTokenExchangeRequest() sends no scope. Fix: build the exchange request explicitly with a single-resource scope.
"Invalid ID Token" / nonce mismatch — hand-building the exchange request for (2) must replicate every field createTokenExchangeRequest() sets, including the nonce AppAuth validates the id_token against (and the PKCE code verifier). Fix: carry the nonce + verifier over.
Hardening (same flow): guard the previously unguarded createAuthIntent() launch (AppAuth throws ActivityNotFoundException when no browser is available) so it surfaces an error instead of crashing; dispose the AuthorizationService that createAuthIntent() leaked; log sign-in failures via Log.d (auto-stripped from release builds by the existing ProGuard rule).
Test plan
./gradlew :app:assembleDebug green
./gradlew :app:testDebugUnitTest green
Manual: real Outlook sign-in on a physical device completes without crashing; account added, inbox syncs
Not yet exercised: sending Outlook mail (Graph sendMail + SMTP fallback)
## Summary
"Sign in with Microsoft" (Outlook) crashed on the redirect back from the browser and never completed a login. This fixes three layered bugs plus some hardening in the account-setup flow. **Verified end-to-end on a real Outlook account on a physical device** (redirect → token exchange → account added → inbox sync).
## Root causes & fixes (in flow order)
1. **Redirect crash (the reported symptom)** — AppAuth's `RedirectUriReceiverActivity` extends `AppCompatActivity`, so it needs a `Theme.AppCompat` theme. This app is pure Compose (`MainActivity : ComponentActivity`, framework `Theme.Material`), and AppAuth declares that activity with no theme of its own, so it inherited the Material app theme and threw _"You need to use a Theme.AppCompat theme"_ the instant Android launched it to deliver the redirect. **Fix:** give it the translucent AppCompat theme AppAuth itself applies to `AuthorizationManagementActivity`. Build-type independent — not an R8 issue.
2. **Token exchange rejected with `AADSTS70011` ("must include a 'scope' input parameter")** — one consent spans two Microsoft resources (Graph for send, Exchange Online for IMAP), so Microsoft mints one token per resource and the code→token exchange must name a single resource. AppAuth's `createTokenExchangeRequest()` sends no scope. **Fix:** build the exchange request explicitly with a single-resource scope.
3. **"Invalid ID Token" / nonce mismatch** — hand-building the exchange request for (2) must replicate every field `createTokenExchangeRequest()` sets, including the nonce AppAuth validates the id_token against (and the PKCE code verifier). **Fix:** carry the nonce + verifier over.
**Hardening (same flow):** guard the previously unguarded `createAuthIntent()` launch (AppAuth throws `ActivityNotFoundException` when no browser is available) so it surfaces an error instead of crashing; dispose the `AuthorizationService` that `createAuthIntent()` leaked; log sign-in failures via `Log.d` (auto-stripped from release builds by the existing ProGuard rule).
## Test plan
- [x] `./gradlew :app:assembleDebug` green
- [x] `./gradlew :app:testDebugUnitTest` green
- [x] Manual: real Outlook sign-in on a physical device completes without crashing; account added, inbox syncs
- [ ] Not yet exercised: sending Outlook mail (Graph `sendMail` + SMTP fallback)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
"Sign in with Microsoft" (Outlook) crashed on the redirect back from the browser and never completed a login. This fixes three layered bugs plus some hardening in the account-setup flow. Verified end-to-end on a real Outlook account on a physical device (redirect → token exchange → account added → inbox sync).
Root causes & fixes (in flow order)
RedirectUriReceiverActivityextendsAppCompatActivity, so it needs aTheme.AppCompattheme. This app is pure Compose (MainActivity : ComponentActivity, frameworkTheme.Material), and AppAuth declares that activity with no theme of its own, so it inherited the Material app theme and threw "You need to use a Theme.AppCompat theme" the instant Android launched it to deliver the redirect. Fix: give it the translucent AppCompat theme AppAuth itself applies toAuthorizationManagementActivity. Build-type independent — not an R8 issue.AADSTS70011("must include a 'scope' input parameter") — one consent spans two Microsoft resources (Graph for send, Exchange Online for IMAP), so Microsoft mints one token per resource and the code→token exchange must name a single resource. AppAuth'screateTokenExchangeRequest()sends no scope. Fix: build the exchange request explicitly with a single-resource scope.createTokenExchangeRequest()sets, including the nonce AppAuth validates the id_token against (and the PKCE code verifier). Fix: carry the nonce + verifier over.Hardening (same flow): guard the previously unguarded
createAuthIntent()launch (AppAuth throwsActivityNotFoundExceptionwhen no browser is available) so it surfaces an error instead of crashing; dispose theAuthorizationServicethatcreateAuthIntent()leaked; log sign-in failures viaLog.d(auto-stripped from release builds by the existing ProGuard rule).Test plan
./gradlew :app:assembleDebuggreen./gradlew :app:testDebugUnitTestgreensendMail+ SMTP fallback)🤖 Generated with Claude Code