Skip to content

fix: add scope to Microsoft OAuth callback#1508

Closed
tembo[bot] wants to merge 1 commit into
devfrom
tembo/fix-microsoft-oauth-invalid-scope
Closed

fix: add scope to Microsoft OAuth callback#1508
tembo[bot] wants to merge 1 commit into
devfrom
tembo/fix-microsoft-oauth-invalid-scope

Conversation

@tembo
Copy link
Copy Markdown
Contributor

@tembo tembo Bot commented May 27, 2026

Summary

Add support for including the 'scope' parameter in Microsoft OAuth callback token exchange to fix invalid_scope error.

  • Thread providerScope as extraScope to getCallback in OAuth callback handler.
  • Implement getOAuthCallbackExtras to conditionally add merged scopes to token exchange request.
  • Enable includeScopeInCallbackTokenRequest flag in Microsoft provider to include scope in token exchange.
  • Add tests for getOAuthCallbackExtras behavior.

This resolves the AADSTS70011 error caused by missing 'scope' in token exchange request for Microsoft OAuth.


Want tembo to make any changes? Add a comment with @tembo and i'll get back to work!

View on Tembo View Agent Settings


Summary by cubic

Fix Microsoft OAuth invalid_scope (AADSTS70011) by sending the merged scope in the callback token exchange. Scoped to Microsoft only; other providers are unchanged.

  • Bug Fixes
    • Pass providerScope as extraScope to getCallback in the OAuth callback.
    • Add getOAuthCallbackExtras to merge scopes and include them in the token exchange when enabled.
    • Enable includeScopeInCallbackTokenRequest: true for the Microsoft provider.
    • Add unit tests for getOAuthCallbackExtras.

Written for commit 24f28da. Summary will update on new commits.

Review in cubic

…hange

Co-authored-by: Konsti <n2d4xc@gmail.com>
@tembo tembo Bot added the tembo Pull request created by Tembo label May 27, 2026
@tembo tembo Bot requested a review from N2D4 May 27, 2026 23:00
@tembo
Copy link
Copy Markdown
Contributor Author

tembo Bot commented May 27, 2026

Requesting review from @N2D4 who has experience with the following files modified in this PR:

  • .claude/CLAUDE-KNOWLEDGE.md
  • apps/backend/src/oauth/providers/base.tsx
  • apps/backend/src/oauth/providers/base.test.ts
  • apps/backend/src/oauth/providers/microsoft.tsx
  • apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx

@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-auth-internal-tool Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-auth-mcp Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-auth-skills Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-backend Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-dashboard Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-demo Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-docs Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-preview-backend Ready Ready Preview, Comment May 27, 2026 11:10pm
stack-preview-dashboard Ready Ready Preview, Comment May 27, 2026 11:10pm

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 27, 2026

Greptile Summary

Fixes the AADSTS70011 error for Microsoft OAuth by including the scope parameter in the authorization-code token exchange request — a Microsoft-specific requirement that other providers don't need.

  • Adds getOAuthCallbackExtras to OAuthBaseProvider and a new includeScopeInCallbackTokenRequest flag so only opted-in providers (currently Microsoft) append scope to the token exchange body.
  • Threads providerScope from the outer OAuth info through the callback route into getCallback, ensuring any user-requested extra scopes are merged with the base scope before being sent.
  • Enables the flag in MicrosoftProvider and adds unit tests for the new helper; documents the fix pattern in CLAUDE-KNOWLEDGE.md.

Confidence Score: 4/5

Safe to merge; the change is narrowly scoped to Microsoft and leaves all other providers' token exchange behavior unchanged.

The implementation is clean and well-isolated. getOAuthCallbackExtras returns undefined for providers that don't opt in, so the existing params spread passes undefined as the optional fourth argument — harmless for all other providers. The only gap is a missing test for the common no-extra-scope Microsoft path, where includeScopeInCallbackTokenRequest is true but extraScope is absent.

apps/backend/src/oauth/providers/base.test.ts — the no-extra-scope case for getOAuthCallbackExtras is untested.

Important Files Changed

Filename Overview
apps/backend/src/oauth/providers/base.tsx Adds getOAuthCallbackExtras helper and includeScopeInCallbackTokenRequest flag; correctly threads optional scope into the token exchange only when the flag is set, leaving all other providers unaffected.
apps/backend/src/oauth/providers/microsoft.tsx Opts Microsoft into scope inclusion at token exchange by setting includeScopeInCallbackTokenRequest: true; the field appears before the ...options spread so the caller-provided options type cannot accidentally override it.
apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx Threads providerScope from OAuthOuterInfo into getCallback as extraScope; minimal, targeted change with no other side-effects.
apps/backend/src/oauth/providers/base.test.ts Adds unit tests for getOAuthCallbackExtras; covers the opt-in and dedup cases but is missing a test for includeScopeInCallbackTokenRequest: true with no extraScope.
.claude/CLAUDE-KNOWLEDGE.md Documents the AADSTS70011 fix strategy for future AI-assisted sessions; no code impact.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant CallbackRoute as OAuth Callback Route
    participant MicrosoftProvider
    participant getOAuthCallbackExtras
    participant MSTokenEndpoint as Microsoft Token Endpoint

    Browser->>CallbackRoute: "GET /callback/microsoft?code=..."
    CallbackRoute->>CallbackRoute: Read providerScope from OAuthOuterInfo
    CallbackRoute->>MicrosoftProvider: "getCallback({ extraScope: providerScope, ... })"
    MicrosoftProvider->>getOAuthCallbackExtras: "{ baseScope, extraScope, includeScopeInCallbackTokenRequest: true }"
    getOAuthCallbackExtras-->>MicrosoftProvider: "{ exchangeBody: { scope: "User.Read openid [extra]" } }"
    MicrosoftProvider->>MSTokenEndpoint: "POST /token (code + scope=...)"
    MSTokenEndpoint-->>MicrosoftProvider: access_token + id_token
    MicrosoftProvider-->>CallbackRoute: "{ userInfo, tokenSet }"
    CallbackRoute-->>Browser: Redirect with session
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/backend/src/oauth/providers/base.test.ts:113-124
Missing test for the common case where a Microsoft user has no extra scopes. When `includeScopeInCallbackTokenRequest` is `true` but `extraScope` is absent, the function should still return `exchangeBody.scope` containing just the base scope. Without this test, a future refactor of the `extraScope || ""` fallback could silently drop the scope from the token exchange body in the default (no extra scopes) flow.

```suggestion
  it("adds the merged scope to callback token exchange extras when enabled", () => {
    expect(getOAuthCallbackExtras({
      baseScope: "User.Read openid",
      extraScope: "Mail.Read User.Read",
      includeScopeInCallbackTokenRequest: true,
    })).toEqual({
      exchangeBody: {
        scope: "User.Read openid Mail.Read",
      },
    });
  });

  it("uses only baseScope when no extraScope is provided and includeScopeInCallbackTokenRequest is true", () => {
    expect(getOAuthCallbackExtras({
      baseScope: "User.Read openid",
      includeScopeInCallbackTokenRequest: true,
    })).toEqual({
      exchangeBody: {
        scope: "User.Read openid",
      },
    });
  });
});
```

Reviews (1): Last reviewed commit: "fix(oauth): include scope param in Micro..." | Re-trigger Greptile

Comment on lines +113 to +124
it("adds the merged scope to callback token exchange extras when enabled", () => {
expect(getOAuthCallbackExtras({
baseScope: "User.Read openid",
extraScope: "Mail.Read User.Read",
includeScopeInCallbackTokenRequest: true,
})).toEqual({
exchangeBody: {
scope: "User.Read openid Mail.Read",
},
});
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing test for the common case where a Microsoft user has no extra scopes. When includeScopeInCallbackTokenRequest is true but extraScope is absent, the function should still return exchangeBody.scope containing just the base scope. Without this test, a future refactor of the extraScope || "" fallback could silently drop the scope from the token exchange body in the default (no extra scopes) flow.

Suggested change
it("adds the merged scope to callback token exchange extras when enabled", () => {
expect(getOAuthCallbackExtras({
baseScope: "User.Read openid",
extraScope: "Mail.Read User.Read",
includeScopeInCallbackTokenRequest: true,
})).toEqual({
exchangeBody: {
scope: "User.Read openid Mail.Read",
},
});
});
});
it("adds the merged scope to callback token exchange extras when enabled", () => {
expect(getOAuthCallbackExtras({
baseScope: "User.Read openid",
extraScope: "Mail.Read User.Read",
includeScopeInCallbackTokenRequest: true,
})).toEqual({
exchangeBody: {
scope: "User.Read openid Mail.Read",
},
});
});
it("uses only baseScope when no extraScope is provided and includeScopeInCallbackTokenRequest is true", () => {
expect(getOAuthCallbackExtras({
baseScope: "User.Read openid",
includeScopeInCallbackTokenRequest: true,
})).toEqual({
exchangeBody: {
scope: "User.Read openid",
},
});
});
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/src/oauth/providers/base.test.ts
Line: 113-124

Comment:
Missing test for the common case where a Microsoft user has no extra scopes. When `includeScopeInCallbackTokenRequest` is `true` but `extraScope` is absent, the function should still return `exchangeBody.scope` containing just the base scope. Without this test, a future refactor of the `extraScope || ""` fallback could silently drop the scope from the token exchange body in the default (no extra scopes) flow.

```suggestion
  it("adds the merged scope to callback token exchange extras when enabled", () => {
    expect(getOAuthCallbackExtras({
      baseScope: "User.Read openid",
      extraScope: "Mail.Read User.Read",
      includeScopeInCallbackTokenRequest: true,
    })).toEqual({
      exchangeBody: {
        scope: "User.Read openid Mail.Read",
      },
    });
  });

  it("uses only baseScope when no extraScope is provided and includeScopeInCallbackTokenRequest is true", () => {
    expect(getOAuthCallbackExtras({
      baseScope: "User.Read openid",
      includeScopeInCallbackTokenRequest: true,
    })).toEqual({
      exchangeBody: {
        scope: "User.Read openid",
      },
    });
  });
});
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tembo Pull request created by Tembo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants