You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Unconditional prompt=consent (when offline_access in scope) blocks OAuth in Entra tenants with user consent disabled + admin consent granted #2867
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.
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.
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:
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.
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.
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.
Activity
dasjideepak commentedon Sep 26, 2026
Reproduced on
main(the same code is in 1.30.1): withoffline_accessin the scope,startAuthorization()always addsprompt=consentand there is no way to turn it off. I opened #2877 with an opt-out flag; the default is unchanged.Ben-Home commentedon Sep 28, 2026
Good RCA. The root cause is that
prompt=consentis unconditional wheneveroffline_accessis in scope, but consent prompting is only needed when the client actually lacks the grants.A few angles:
The forced
prompt=consentconflates 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.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.
SDK-level options worth proposing upstream:
promptConsentOnOfflineAccess: false(default preserved for compatibility).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.
pcarleton commentedon Oct 21, 2026
👍 i'm supportive of an opt-out flag