Skip to content

Unconditional prompt=consent (when offline_access in scope) blocks OAuth in Entra tenants with user consent disabled + admin consent granted #2867

Closed

Description

@sonmaximum

Symptom

packages/client/src/client/auth.ts, in startAuthorization:

if (scope?.split(' ').includes('offline_access')) {
    authorizationUrl.searchParams.append('prompt', 'consent');
}

In enterprise Entra tenants where:

  • User consent is disabled (Microsoft's recommended default)
  • Admin consent granted tenant-wide for the app (AllPrincipals OAuth2PermissionGrant present)

prompt=consent bypasses the existing admin grants and forces Entra to show the consent screen. Non-admin users hit AADSTS90095: Admin consent is required on every reconnect. Unrecoverable, since the admin already consented. Approving the resulting admin-consent request does not change tenant state; the next connect triggers another same consent request.

Verified in our tenant against a Microsoft-hosted MCP server (WorkIQ Mail Tool): manually removing &prompt=consent from the SDK-generated authorize URL and continuing the flow returns tokens immediately, no consent screen. Grants are respected; the SDK-added parameter is the sole blocker.

Repro

Cline (using this SDK): blocks with AADSTS90095:

https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize
  ?response_type=code
  &client_id=<redacted>
  &redirect_uri=http://127.0.0.1:1456/mcp/oauth/callback
  &scope=<resource>/.default+openid+profile+offline_access
  &prompt=consent   ← added by SDK, sole blocker
  &resource=<resource>

The &prompt=consent in this URL is what the SDK code shown above appends. Removing it manually (per the Symptom section above) returns tokens with no consent screen - confirming it as the sole blocker.

Fix

Drop the unconditional add.

Spec basis: OIDC Core §11's prompt=consent obligation is on the authorization server, not the client. §11 notes that prompt=consent "can be used" by the AS - not required to be sent by the client. #681 introduced the client-side add as a defensive measure; the underlying spec doesn't ask for it.

Precedent: Azure CLI, VS Code, and other established OAuth clients requesting offline_access against Entra don't force the prompt=consent parameter client-side and operate correctly in the same enterprise-tenant configuration where this SDK currently breaks.

Downstream clients that want to force re-consent for a specific high-risk flow can add the parameter themselves at the call site.

Impact

Blocks every non-admin end-user in enterprise Entra tenants for any MCP server the client OAuths against.

Related:

Activity

  1. added
    v1Issues / PRs related to v1.x
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Sep 25, 2026
  2. dasjideepak commented on Sep 26, 2026

    @dasjideepak
    Contributor

    Reproduced on main (the same code is in 1.30.1): with offline_access in the scope, startAuthorization() always adds prompt=consent and there is no way to turn it off. I opened #2877 with an opt-out flag; the default is unchanged.

  3. Ben-Home commented on Sep 28, 2026

    @Ben-Home

    Good RCA. The root cause is that prompt=consent is unconditional whenever offline_access is in scope, but consent prompting is only needed when the client actually lacks the grants.

    A few angles:

    1. The forced prompt=consent conflates two things: asking for the refresh scope, and re-confirming consent the tenant has already recorded. In an Entra tenant with an AllPrincipals grant (admin consent), any prompt parameter that forces the interactive consent screen bypasses the recorded grant and produces AADSTS90095 for non-admins. This is the general interaction between prompt=consent and pre-granted scopes, not specific to this SDK.

    2. Short-term unblock without SDK changes: the issuer can adjust the authorize URL through the MCP client's URL override where the client supports it, or drop offline_access from the requested scope list when the refresh token is not actually required.

    3. SDK-level options worth proposing upstream:

      • A client option like promptConsentOnOfflineAccess: false (default preserved for compatibility).
      • Only appending prompt=consent when the client is certain no prior grant exists is not reliably detectable, so an opt-out flag is the pragmatic shape.
      • Documenting that servers running in enterprise tenants should advertise offline_access only when refresh flows are actually supported.

    The OAuth 2.1 direction treats consent prompting as an issuer policy concern rather than something a client should force per request, which supports the opt-out approach. We hit the same class of refresh-scope behavior in our own MCP server tooling (https://corpusiq.io) and landed on an opt-out flag plus a warning when the provider is a known enterprise IdP. Happy to comment on a PR if a maintainer takes the flag approach.

  4. pcarleton commented on Oct 21, 2026

    @pcarleton
    Member

    👍 i'm supportive of an opt-out flag

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions