Outlook-plugin: token-refresh debuggen
Aan het einde van deze gids weet je waar in de stack een specifieke gebruikersklacht (“plugin werkt niet meer”, “moet steeds opnieuw inloggen”, “krijgt ‘koppel opnieuw’ melding”) vandaan komt, en kun je gericht actie ondernemen.
Voor de architecturele context (waarom de flow zo is), zie Explanation: Outlook-plugin. Voor de volledige event-tabel, zie Reference: log-events.
Klacht-tot-event matrix
| Klacht | Verwachte logs | Wat te checken |
|---|---|---|
| “Moet elke maand opnieuw inloggen” | session/refresh 401 in API-logs of geen refresh-aanroep |
Slide-refresh aan client-zijde gebroken; check of outlook-plugin-auth.js correct geladen wordt en pendingSlideRefresh niet vastloopt |
| “Krijgt ‘koppel opnieuw’ (EXACT_REAUTH_REQUIRED)” | exact.outlook.refresh.invalid_grant of .threshold_revoked voor deze exactUserId |
Check of refresh_token verlopen is (kan na lange inactiviteit) of dat een serie transient fouten de threshold heeft geraakt |
| “Probeer opnieuw-knop doet niets” | Geen netwerk-call zichtbaar in plugin-DevTools | Zie de fix-historie van #2204; controleer of outlook-plugin.js retryLoadCurrentItem niet stil bestaande UI hidet |
| “Random 401’s op willekeurige acties” | Verspreide exact.outlook.refresh.transient events |
Mogelijk Exact-side rate-limit of korte downtime; check Exact-status pagina |
| “Werkt elke ochtend even niet” | exact.outlook.refresh.batch met hoge rateLimited count tussen 02:00-03:00 UTC |
Exact rate-limits ‘s nachts; geen actie nodig, cron-tick herstelt vanzelf |
Stappen voor een specifieke gebruiker
1. Vind de exactUserId
Vraag de gebruiker zijn Outlook-emailadres. Query in mongo (outlookexacttokens-collectie):
db.outlookexacttokens.findOne({ outlookEmail: 'user@example.com' })
Resultaat geeft exactUserId, tenantUuid, revokedAt, reauthRequiredAt,
refreshFailureCount, lastUsedAt, lastRefreshedAt. Eerste check:
revokedAt: null+reauthRequiredAt: null+refreshFailureCount: 0→ gezonde sessie. Klacht zit waarschijnlijk client-side of in een ander deel van de flow.revokedAt: <datum>→ token is server-side ingetrokken. Zoek terug in logs waarom (zie volgende stap).reauthRequiredAt: <datum>→ Exact meldde “Old refresh token used” (verweesd refresh_token). De doc is NIET gerevoked maar valt uit de refresh-cron; de gebruiker moet opnieuw koppelen. Re-OAuth (upsert) wist het veld. Zet dit veld nooit handmatig op null zonder verse tokens, anders herleeft de cron-loop.refreshFailureCount >= 30→ klop tegen REVOKE_THRESHOLD; sessie wordt bij de eerstvolgende cron of API-call gerevoked.lastUsedAtouder dan 60 dagen →cleanup-outlook-exact-tokensrevoked dit doc; gebruiker moet opnieuw koppelen.
2. Volg de refresh-events in Loki
{app="tapster-api"} |= "exact.outlook.refresh" | json | exactUserId="<id>"
Tijdslijn van events laat zien wat er gebeurde rond het moment van klacht. De verwachte gezonde flow voor een actieve gebruiker:
.success every ~10 min (cron of on-demand)
.stale occasional (race-loss; geen probleem)
Verdachte patronen:
.transient × N, .threshold_revoked → auto-revoke
.invalid_grant → refresh_token kapot (gebruiker moet opnieuw koppelen)
.reauth_required (eenmalig) → verweesd refresh_token; doc krijgt `reauthRequiredAt` en valt uit de cron. Gebruiker moet opnieuw koppelen
.reauth_required herhaaldelijk (~1x/min) → cron-loop: `reauthRequiredAt` wordt niet gezet/gerespecteerd. Dit was de bug vóór de reauthRequiredAt-fix; bij terugkeer check of findExpiringSoon nog op `reauthRequiredAt: null` filtert
.rate_limited × veel → Exact-rate-limit, vanzelf weg
.race_lost herhaaldelijk → 2 servers proberen elk tegelijk te refreshen (acceptabel). Check `attempts`: 1 = direct gedetecteerd, 2-4 = via retry-window opgevangen (zie `RACE_DETECTION_BACKOFF_MS` in `exactService.ts`)
3. Check client-side state
Vraag de gebruiker DevTools te openen op de plugin (Outlook → klik in taakvenster → rechtsklik → Inspect). In de Console:
// Toont de huidige JWT-sessie (exp = ms-epoch)
JSON.parse(localStorage.getItem('tapsterOutlookSession'))
// Office-context aanwezig?
typeof Office !== 'undefined' && Office.context && Office.context.mailbox && Office.context.mailbox.item
// Auth-module geladen?
window.TapsterOutlookAuth
Veelvoorkomende bevindingen:
localStorage.tapsterOutlookSessionisnullna een refresh-loop →tryServerSideSessionRecoveryheeft het opnieuw moeten opbouwen; check Network-tab opPOST /exact/outlook/session/recover.Office.context.mailbox.itemis null → Outlook-cache-issue; gebruiker moet de mail opnieuw openen of plugin herstarten.
4. Forceer een refresh in DB
Voor handmatige acties (alleen via een dev-tool of geverifieerde admin-shell; niet in productie zonder reden):
// Forceer near-expiry zodat de volgende API-call een refresh triggert
db.outlookexacttokens.updateOne(
{ exactUserId: '<id>' },
{ $set: { expiresAt: new Date(Date.now() + 10_000) } }
)
Daarna een endpoint vanuit de plugin aanroepen (of de cron-tick afwachten);
binnen 10 seconden zie je een exact.outlook.refresh.success of een
faal-event.
Logs filteren voor een tenant
Niet altijd is een specifieke gebruiker bekend. Voor tenant-wide patronen:
{app="tapster-api"} |= "exact.outlook.refresh" | json | tenantUuid="<uuid>"
Rare clustering — bv. 50 invalid_grant events binnen een paar minuten —
suggereert een Exact-API-incident of een tenant-brede revoke (admin-actie).
Wanneer escaleren
invalid_grantvoor één gebruiker: gebruiker moet opnieuw koppelen. Geen actie nodig dan een vriendelijk bericht.invalid_grantvoor veel gebruikers tegelijk: contacteer Exact, mogelijk hebben ze refresh_tokens client-side ingetrokken via App Center.threshold_revokedvoor één gebruiker: kijk waar de transient-flood vandaan kwam (.transient-events). Vaak een Exact-incident waarvan we de cleanup pas later zien. Reset desnoods via$set: { refreshFailureCount: 0, revokedAt: null }als Exact-side al weer werkt.- Geen
outlookexacttokens-doc voor een gebruiker die zegt ‘gekoppeld’ te zijn: ofweloutlookEmailis anders dan verwacht, ofwel doc is viadeleteOldRevoked(>90d) weggegooid. Gebruiker moet opnieuw koppelen.