-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Azure (Entra) OAuth: Validate token against FastMCP app, not Graph. #1891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I'd prefer this to be removed, as the current implementation
I'll read through code tomorrow. |
Agree, just wasn't sure if removing it was too much of a breaking change. |
|
I've updated the PR to remove validating against Graph entirely and only validate against the MCP server app it self. Want me to update the PR description to reflect this? I believe the |
|
Looks great! I'm not that involved in the protocol (especially DCR), so maybe you guys could fill me in: How does AI clients, e.g. Claude, Claude Code or even MCP inspector deal with refreshing of tokens? Requesting an |
|
@JonasKs refresh flows should be identical to normal OAuth flow; DCR only adjusts how clients are registered. The [dynamic] client receives a "normal" token + refresh token and from then on uses standard flows. |
|
@jlowin & @nbaju1 , then I believe this solution will still have flaws, and we need add a parameter |
|
@JonasKs we can add special handling for the Graph scopes. Add a new parameter |
|
Hmm.. I like:
I’ve mentioned this in my issue, but I think scope validation (and user role checks) should be on a per tool/resource/prompt/route basis, like in FastAPI with dependency injection. |
|
I'm not familiar with other IDPs, so if this is a pattern that is standard, i.e. requesting scopes from the IDP that isn't returned in the access token, I agree that the name should be more general. I vote for getting the provider to work in this PR, then see about separating out the scope validation in a subsequent PR. |
|
I have no idea either to be honest. I'm on board with this! |
|
I was able to get this branch working with my Azure tenant. I was initially getting token rejections: "Bearer token rejected for client" I enabled debug logs, and enhanced the # src/fastmcp/server/auth/providers/jwt.py
if self.issuer:
if claims.get("iss") != self.issuer:
self.logger.debug(
"Token validation failed: issuer mismatch for client %s. Issuer expected: %s, Issuer received: %s",
client_id,
self.issuer,
claims.get("iss")
)
self.logger.info("Bearer token rejected for client %s.", client_id)
return NoneIt was complaining about the issuer:
I was getting a V1 token instead of a V2 token. To fix this, I needed to edit my Azure App Registration's manifest to include: {
"accessTokenAcceptedVersion": 2
}I read that this may be changing to |
|
Yeah, this is Azure in a nut shell. The manifests has two versions, and which version you see can vary from app reg to app reg. |
|
@JonasKs I faced similar issue when using a different app reg. Can we also parametrize issuer in AzureProvider class?
|
|
I would strongly suggest not supporting v1 tokens, and rather document how to enforce v2. V1 is deperecated, and has a whole lot of extra problems to deal with. The code will end up with lots of |
@nbaju1 @JonasKs This is looking great. I've just been testing this PR as I'm doing some MCP work with Azure Health Data Services. The OAuth flow seems to be working up until querying my the AHDS API. It's giving back https://learn.microsoft.com/en-us/azure/healthcare-apis/authentication-authorization#access-token Maybe issuer does need to be configurable? |
|
Request v2 tokens, its described above. V1 is deprecated. |
Thanks for the quick response. I might be trying to do something that's not supported, and don't want to derail the PR discussion, so if its not obvious adon't let me get in the way. I'm using "on behalf of flow" ( https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow ) I have a client application, which as per the PR docs has: This then has API permissions on a resource app (the Azure service) . I have the resource app scopes configured under It's the resource app token that is coming back as the v1 token. |
|
@JonasKs ignore that, sorted it, I've set Be great to see this PR released. Thanks @nbaju1! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description
Many Azure integrations target a custom API rather than Microsoft Graph. The existing provider always validates tokens via Graph and forwards the RFC 8707 resource parameter, which v2.0 endpoints ignore. This change adds first-class support for custom API audiences and aligns authorization requests with Azure v2.0 semantics.
audience(your API’s Application ID URI) andapi_client_id(the API app’s GUID used as aud). Whenaudienceis set, the provider usesJWTVerifierwith the tenant-scoped issuer and JWKS (/v2.0issuer,/discovery/v2.0/keys) and validatesaudagainstapi_client_id. It also validates configuration:api_client_idis required whenaudienceis set, andrequired_scopesmust not be prefixed with the audience. The authorization flow drops the unsupportedresourceparameter and prefixes non-OpenID scopes with<audience>/, always ensuringopenidis included. Microsoft Graph behavior remains the default and unchanged whenaudienceis not provided.Backwards compatibility: existing Graph-based setups continue to use Graph verification and the previous default scopes. Tests cover the new JWT path (issuer, JWKS, audience, default scopes) and the updated
authorizebehavior (resource removal and scope prefixing).Contributors Checklist
Review Checklist
Claude Opus 4.1 and GPT-5 used in generating code and PR description
Closes #1663
Closes #1846
Closes #1925