The Outlook POP3 refresh token expires periodically. This documents the full renewal flow introduced June 2026, replacing the old redirect-based WSGI script (msauth).
Token renewal uses Microsoft device flow (same as the CLI script get_pop_refresh_token.py). No redirect URI registration is required.
Visit: https://www.critchley.biz/outlook
The page (served by wsgi_outlook_oauth.py on gravlax) will:
1. Show a short code in a large box (click to copy)
2. Offer a button to open Microsoft Device Login in a popup
3. Poll every 2 seconds in the background
4. When auth completes, show confirmation and save token to WebDAV
Token is saved to: /var/www/webdav/outlook_tokens/jsr_critchley_at_hotmail_com.json
File contains: token_type, scope, expires_in, access_token, refresh_token, user_email, saved_at
When popit3.py runs on pomelo and the refresh token fails (expired grant), it:
1. Catches the RuntimeError from acquire_access_token_via_refresh
2. Calls fetch_refresh_token_from_webdav(user_email) — HTTP GET to https://webdav.critchley.biz/outlook_tokens/jsr_critchley_at_hotmail_com.json with Basic Auth from ~/.netrc (machine webdav.critchley.biz)
3. Calls update_netrc_refresh_token(machine, new_token) — rewrites the password field for outlook.office365.com in ~/.netrc
4. Retries acquire_access_token_via_refresh with the new token and continues normally
Deployed to: /usr/local/www/wsgi-scripts/wsgi_outlook_oauth.py
Apache config: WSGIScriptAlias /authorize-outlook /usr/local/www/wsgi-scripts/wsgi_outlook_oauth.py
Short URL redirect: RedirectMatch ^/outlook$ /authorize-outlook/authorize
Routes:
• /authorize-outlook/authorize — initiate device flow, show code + button
• /authorize-outlook/status?flow_id=X — single-shot poll (non-blocking POST to MS token endpoint)
• /authorize-outlook/list — show stored tokens
Flow state stored in /tmp/outlook_wsgi_flows/{sha256_of_device_code[:16]}.json (disk, not memory, so all Apache worker processes can access it)
Functions added:
• fetch_refresh_token_from_webdav(user_email) — fetches JSON from WebDAV, returns refresh_token field
• update_netrc_refresh_token(machine, new_token) — updates password in ~/.netrc, preserves all other fields, sets permissions 600
Fallback only triggers when refresh token exchange raises RuntimeError (not on cached access token expiry).
machine outlook.office365.com
login jsr_critchley@hotmail.com
account MSAL:60da67f7-5fde-4e85-baf3-ab28d0c8e034
password <refresh_token>
machine webdav.critchley.biz
login john
password <webdav_password>
Access tokens are cached in ~/.outlook.office365.com_token (JSON, valid for ~3600s). The refresh token fallback only runs when this cache is absent or expired AND the refresh token exchange fails.
App: Popit2 (60da67f7-5fde-4e85-baf3-ab28d0c8e034). Device flow requires Allow public client flows enabled. No redirect URI registration needed for device flow.
If the Microsoft device-login popup opens but stays blank (stuck on "Transferring data from login.live.com..."), check for Firefox Enhanced Tracking Protection or a similar ad/tracker-blocking extension. Confirmed 2026-07-08: ETP was silently blocking requests to logincdn.msauth.net (the CSS/JS the login page needs to render), leaving the page permanently blank.
Fix: click the shield icon in the Firefox address bar and turn off Enhanced Tracking Protection for login.live.com (or whitelist logincdn.msauth.net in the blocking extension).
Diagnose via Firefox DevTools > Network tab: blocked requests show status 0 with no timing data at all (blocked client-side before hitting the network), while the main login.live.com document itself loads fine (200 OK).