Skip to content

Read the profile from the token for Outlook-only scopes - #57

Open
fjaeger wants to merge 1 commit into
synth:mainfrom
Mailbutler:outlook-office-token-profile
Open

fjaeger wants to merge 1 commit into
synth:mainfrom
Mailbutler:outlook-office-token-profile

Conversation

@fjaeger

@fjaeger fjaeger commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

When the requested scopes only target Outlook resources, for example `offline_access https://outlook.office.com/SMTP.Send\` or `https://outlook.office.com/IMAP.AccessAsUser.All\` for XOAUTH2 SMTP/IMAP, the access token's audience is `outlook.office.com`. `GET https://graph.microsoft.com/v1.0/me\` then fails with 401 and the callback errors out.

This change:

  • `determine_profile_endpoint` returns `nil` when every non-base scope is an `outlook.office.com` / `outlook.office365.com` resource. In that case `raw_info` is built from the access token's claims instead of calling an API. The claims are mapped onto Graph's field names (`id` ← `oid`, `givenName`, `surname`, `displayName`, `mail`, `userPrincipalName` ← `upn`), so `uid`, `info` and the domain verifier keep working unchanged.
  • `determine_profile_endpoint` now falls back to the scope configured on the strategy when the request doesn't pass one; scopes are usually set in the provider options.
  • `profile_endpoint` uses `defined?` instead of `||=`, so a determined `nil` isn't overwritten with the Graph URL.

The claims are decoded without signature verification. The token was just received from Microsoft's token endpoint over TLS, and Microsoft doesn't publish keys for verifying tokens issued for its own APIs.

With these scopes the auth hash usually has no email. With `skip_domain_verification` enabled, that depends on the nil guard in #56.

🤖 Generated with Claude Code

A token requested only for Outlook resources (for example
https://outlook.office.com/SMTP.Send or IMAP.AccessAsUser.All) has
outlook.office.com as its audience, so GET graph.microsoft.com/v1.0/me
fails with 401 and the callback errors out.

For such scopes, skip the profile request and build raw_info from the
access token's claims, mapped onto Graph's field names so 'info' and the
domain verifier keep working. The scope configured on the strategy is
now also considered when the request does not pass one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant