Preventing Confused Deputy Attacks In MCP With Azure API Management
Table of Contents
I want to talk about a threat vector in MCP servers that was first discussed by Will Bartlett (of Microsoft, he helped with our improved authorization spec) in relation to Cloudflare’s remote server implementation (now improved), but really - it’s applicable and will be applicable to other remote servers as well. The threat vector is a problem commonly referred to as “confused deputy,” and you will see why in a second.
It’s all about Dynamic Client Registration #
In the world of protected MCP servers, and especially those that do follow the authorization specification, the MCP servers themselves need to either rely on Authorization Servers (referred to as AS) that support Dynamic Client Registration (DCR) to make the MCP client-to-server interactions work. The motivation behind this is best covered by Aaron Parecki in his recent blog post:
The MCP spec strongly recommends that MCP Servers (authorization servers) support Dynamic Client Registration. If MCP is successful, there will be a large number of MCP Clients talking to a large number of MCP Servers, and the user is the one deciding which combinations of clients and servers to use. This means it is not scalable to require that every MCP Client developer register their client with every MCP Server.
The challenge is that most authorization servers out there do not yet support DCR. We still need to solve the client registration problem, so what we see a lot of developers do is essentially have their MCP server (referred to as Resource server, or RS) act as a “faux-AS.”
What that means in practice is that instead of having the client talk to an established AS (e.g., one exposed by Entra ID or Okta), that will issue a token for the MCP server, the developer of the MCP server implements their own /authorize
, /token
, and /register
endpoints. They have a static client ID that is used by the server to acquire tokens from an actual AS downstream, and they issue their own JWT tokens (or any tokens, for that matter, that are client-ready) back to the MCP client.
In this process, the user of the MCP client is, in fact, redirected to the authorization page from the downstream AS, however the token is returned to the MCP server and not the MCP client directly. The server will map the acquired token to some kind of “session” and then return the token representation back to the client. Mind you, these must not be the same as the downstream AS-issued tokens.
Oddly reminiscent of the 2025-03-26
version of the spec? Developers do this because they need to somehow implement DCR.
The problem #
With this implementation, however, comes a very dangerous pitfall that you need to consider. Let’s say I maintain a MCP server called Mini MCP Server that gates access to it behind Entra ID. Entra ID does not support DCR, so I have to implement DCR myself - within the MCP server.
My server uses a static client ID within itself that is used for authorization. When someone hits my /authorize
endpoint (remember, I have a “faux-AS”), I will redirect them to Entra ID, have the user go through the process, and return the authorization code back to me, the Mini MCP Server to be exchanged for a token. I’ll even follow good security practices and make sure that the client never sees the Entra ID token - I will issue it a “session token” that I map in my own cache.
But here is the danger:
- My DCR endpoint (
/register
) does not require authorization. Anyone can hit it and register a client, by design. - My user comes from a totally legit MCP client. Let’s call it Awesome MCP Client. They kick off the authorization process from it.
- Mini MCP Server sees an inbound client registration request. It takes the metadata (
Awesome MCP Client
with a redirect URI ofhttps://mcp.example.com
) and issues a client ID to the client. The client is now registered. - Mini MCP Server then redirects the authorization request to Entra ID, with its static client ID (not the same as the freshly-issued DCR client ID for Awesome MCP Client).
- The user goes through the flow in their browser and authorizes the application with the requested scopes. A cookie is set in the browser that the user is now authenticated with Entra ID. This last part here is critical.
- A malicious individual also hits up my registration endpoint (
/register
) with their own client metadata (Awful MCP Client
with a redirect URI ofhttps://bad.example.com
). My MCP server is none the wiser - it doesn’t keep track of all client names and domains, and goes ahead and registers a client for this as well. - The individual then sends their victim a crafted link that kicks off the authorization flow with Mini MCP Server, like:
It’s shortened for brevity. The link can be sent through email, Signal, pigeon mail, snail mail, or any other way, really.https://mymcpserver.example.com/authorize? client_id=awful_mcp_client_id &redirect_uri=https%3A%2F%2Fbad.example.com
- The victim opens the link in their browser.
- Mini MCP Server redirects them to Entra ID, because that’s what the process is.
- Entra ID reads the cookie from the browser and matches it up (again, overly simplifying here, but you get the gist) - user is already authenticated and consented to the static Mini MCP Server Entra ID client ID.
- It sends the authorization code to Mini MCP Server.
- Mini MCP Server exchanges the authorization code for a token, and issues a “session token” back to the malicious client (Awful MCP Client).
- The malicious actor now has access to Mini MCP Server with the victim’s account.
13 steps to a really, really, bad scenario. So, what can we do? Normally, I would say “We need proper DCR support,” but that’s not always going to be possible with all the identity providers out there. Instead, we need to apply the approach that Cloudflare used to mitigate the problem - for every client, prompt the user to consent separately from the downstream AS consent.
Extra consent prompt #
The mitigation here is this - when a user initiates the authorization process with my Mini MCP Server, server-side I will check if there is a consent cookie attached to the request. If there is not, that means the user is going through the authorization flow for the first time with a given client. This warrants an interrupt, that looks like this (as seen through a Claude Desktop integration):
This interrupt may feel like somewhat of a bandaid over the problem of clients properly implementing and authorization servers properly supporting Dynamic Client Registration (DCR). That day will come, but in the meantime we need to make sure that we protect customers from being phished.
The implementation of this interrupt is also relatively basic if you look at the existing Azure API Management Policy implementation. The fun thing about this implementation is that it’s capable to handle a lot of the inbound request data out-of-the-box and check whether the client is registered.

Wait, wait, wait… But I am looking at this sample, and I see that Azure API Management is the one handling client registration, and it stores things in a volatile cache. Won’t that be a problem for cookie checking in the future?
Good observation! Indeed, you might’ve noticed that I keep referring to one of my Python samples that shows how to do authorization with MCP servers through Azure API Management. Because this is a sample, all data is stored in a cache that is, by default, somewhat short-lived. For example, you might spot this in one of the policies:
<cache-store-value duration="3600"
key="ClientRedirectUri"
value="@(context.Variables.GetValueOrDefault<JObject>("requestBody")["redirect_uris"][0].ToString())" />
This means that in 3,600 seconds (or, one hour) the entry will vanish. That’s not exactly ready for production, and if you want to implement proper DCR, you will need to move to something more durable, like an actual database.
Not using a database and relying on the cache all while implementing the interrupt above will also cause issues if you connect to Claude Desktop. Once the registration is established, Claude Desktop will cache the registration information locally and will not re-register itself with the MCP server, which means that the cookie you will need for consent will technically exist, but you won’t be able to use it because your server no longer has a record of the registration.
But, back to the consent prompt:

The neat thing about this implementation is that not only does it present the user with details about the requesting client and the redirect URIs, but it also includes scopes that the MCP client will get access to through the MCP server. Because I am using Microsoft Graph and my Mini MCP Server is using User.Read
for its client, that’s what’s shown in the consent dialog. This gives a clear indication to the user as to what level of access they will give the client if they choose to proceed.

All this is good, but this still puts the burden on the user to read the client details and make an informed decision. They can accidentally click Allow and that’s it - the malicious client now has access to the MCP server.
Once again, an astute observation. This is indeed the case, but like I said above - it’s a bandaid over an imperfect solution we have today. Clients do not have a “signed identity” that is required by the downstream API, and neither can they provide one at this time given the MCP server requirements, so we have to assemble a model using the LEGO bricks we have in the box today.
So what’s the better way? #
Once DCR is properly implemented and adopted, the entire flow can go between clients and authorization servers, without MCP servers having to maintain the “mapping” I outlined above or attempt to use a single client ID for downstream operations while acting as “faux-AS.” If you are working with an identity provider (IdP) that doesn’t support DCR and you want to use it in MCP workloads, I would recommend you engage with the owners of said IdP to advocate for the implementation of DCR.
In the meantime, you can check out my sample on how this consent prompt can be implemented:
Check out the codeAnd of course, if you have feedback or comments on the implementation or direction of the MCP authorization stack, leave a comment on this blog post!