Introducing the next era of Duende IdentityServer.

Read our CEO’s announcement

Hardening OAuth in the newest 2026-07-28 MCP Release Candidate

Joe DeCock
Two blue circles
Joe DeCock follows the OAuth working group, the OpenID Foundation, and the IETF and writes up what matters for working developers. Follow Joe on LinkedIn to catch his commentary as these standards evolve, and subscribe to the Duende newsletter using the form at the bottom of this page for implementation guidance, security updates, and deep dives delivered to your inbox.

It's an exciting time for agentic identity standards. A new Model Context Protocol (MCP) release candidate was just published, and it brings many new features to the protocol. The headline feature of the release is that the protocol is going stateless.

In previous MCP versions, there's a handshake at the beginning that creates a session. MCP servers have to track those sessions on the server side and route traffic to the appropriate node for the session. The complexity and cost of sticky sessions and a load balancer create a (ahem) sticky situation for your infrastructure and AI spend. Now that MCP is going stateless, all those challenges go away, and it will be much easier to deploy and scale MCP servers. You can read more about MCP going stateless in the official MCP blog.

But beyond that headline, there are also half a dozen authentication and authorization updates that landed in this release. These Specification Enhancement Proposals, or SEPs, harden the protocol's security in line with existing identity standards and deployments. Each one has some important considerations for anyone responsible for the security of MCP clients and servers.

The 6 Authorization Hardening SEPs (MCP 2026-07-28 RC)

Here are the six new authorization hardening SEPs. For those operating an Authorization Server (AS), the first three in the table are the most relevant. The remaining rows largely consist of rules and guidance for clients and resource servers. It's still good to know about those, especially if you're operating both the IdentityServer instance and the MCP server. All the MCP components ultimately should follow all of this guidance, so I'll touch on all 6, at least briefly.

SEP Title / Topic Action for IdentityServer Implementors

Declare OIDC application_type during Dynamic Client Registration

Use application_type to validate redirect URIs more precisely.

Recommend Issuer (iss) Parameter in MCP Auth Responses (RFC 9207)

Check that EmitIssuerIdentificationResponseParameter is enabled (on by default)

Clarify .well-known discovery suffix

No change necessary. IdentityServer is already correct.

Bind credentials to issuer; re-register on resource migration

N/A: Guidance for clients

OIDC-Flavored Refresh Token Guidance

N/A: Guidance for clients and servers.

Clarify scope accumulation during step-up

N/A: Guidance for clients and servers

Application Type in DCR (SEP-837)

First up is the application_type parameter for Dynamic Client Registration (DCR). This is an OIDC parameter that is used to give a hint about the registering application's nature (web, desktop, etc). Historically, IdentityServer hasn't done anything with that parameter, but this latest MCP spec draft points out that you might want special logic for handling a desktop or CLI client's redirect URIs (which will use localhost).

This is a good idea that allows you to validate the redirect URIs more precisely. To do that validation, create a DynamicClientRegistrationValidator in your IdentityServer implementation. In the validator, you can check the application_type in the Extensions dictionary of the request, like this:

Csharp

// Less commonly used DCR parameters are captured in request.Extensions
var applicationType = request.Extensions.GetValueOrDefault("application_type") 
    as string ?? "web"; // ("web" is the default in the spec)

And then perform validation on the redirect URIs like this:

Csharp

foreach (var uri in context.Request.RedirectUris ?? [])
{
    if (applicationType == "web" &&
        !uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
    {
        return new DynamicClientRegistrationError(
            DynamicClientRegistrationErrors.InvalidRedirectUri,
            "Web clients must use https redirect URIs");
    }

    if (applicationType == "native" &&
        uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) &&
        !IsLoopback(uri))
    {
        return new DynamicClientRegistrationError(
            DynamicClientRegistrationErrors.InvalidRedirectUri,
            "Native clients may only use http with localhost redirect URIs");
    }
}

I think this is a good improvement to MCP that will improve redirect URI validation at the DCR endpoint. That said, using DCR in the first place has numerous problems that are mitigated by ClientID Metadata Documents (CIMD). MCP is encouraging a transition from DCR to CIMD, but the details are a topic for another post.

Issuer Identification (SEP-2468)

Next, MCP is encouraging the use of the iss (issuer) parameter. This parameter was introduced in RFC 9207 in order to defend against mixup attacks.

A mixup attack in OAuth is possible when a client application interacts with multiple ASes, and one of those ASes is controlled by an attacker. Multiple ASes might sound unusual to you. Many traditional web applications and workloads have a single AS Server as their authority, but the MCP ecosystem is an example where that isn't true. Many MCP servers allow multiple ASes and might allow the resource server to register on the fly, so MCP meets the preconditions for a mix-up attack.

But what exactly is a mix-up attack? There are numerous variants, but typically a mix-up attack occurs when a user initiates an OAuth flow with an attacker-controlled AS. This malicious server will then modify the protocol steps to trick the client into sending it authorization codes from a different (honest) AS.

Users won't knowingly interact with malicious ASes, of course, but the attacker can use social engineering techniques to hide their identity and confuse the user, or they might have compromised a legitimate AS.

Regardless of how the attacker finds their victim, once a user initiates an OAuth flow to the attacker-controlled AS, the compromising server will immediately redirect the user's browser to the honest AS, manipulating the protocol parameters so that the authorization request appears valid. Then a seemingly normal OAuth flow proceeds: the user authenticates and grants authorization, and an authorization code is sent back to the client. The client, however, doesn't realize that there was a redirect in the middle. When it receives the authorization code from the honest AS, it will then attempt to exchange that code for a token. But where will it send that code? To the attacker-controlled AS where the protocol flow started. The attacker now possesses the authorization code, which they can either exchange for an access token or use in a code injection attack.

sequenceDiagram
    participant User as User (Browser)
    participant Client as Client App
    participant Attacker as Attacker AS
    participant Honest as Honest AS

    User->>Attacker: 1. Initiate OAuth flow
    Attacker->>Honest: 2. Redirect user to Honest AS
    User->>Honest: 3. Authenticate & grant authorization
    Honest->>Client: 4. Auth code sent back
    Client->>Attacker: 5. Exchange code (WRONG server!)

    Note over Attacker: Attacker Now has the auth code!<br/>Can exchange for access token<br/>or use in code injection attack.

To defend against this attack, the AS must make its identity clear to the client. This is done with the iss parameter in the authorization response. The honest AS includes its issuer identifier in the response to the client along with the authorization code. The client then compares that issuer identifier to what it expected, realizes that the authorization code is not meant for the attacker's AS, and interrupts the flow.

Dr. Daniel Fett's deep dive into the subtleties of mix-up attacks is a post I highly recommend. He goes into detail about variations of the attack and specifics of how the parameters get manipulated by the attacker to fool the honest AS.

IdentityServer has supported RFC 9207 for many years. It is controlled by the EmitIssuerIdentificationResponseParameter configuration option, which is enabled by default. If you're running an MCP server secured by IdentityServer today, that default should be protecting you already. If you've disabled it, consider re-enabling it.

MCP now requires clients to validate the iss parameter if one is sent, and future versions of the protocol will make it mandatory (clients will be required to reject authorization codes that don't come with an iss identifier).

Discovery Clarification (SEP-2351)

Finally, for ASes, there is a minor clarification about the use of OAuth Discovery (RFC 8414). The RFC allows for extensibility in the choice of the suffix in the path to the discovery document, but offers oauth-authorization-server as a default. MCP now officially uses that default suffix and not any others. This is a good change for clarity, but doesn't involve any new behavior in practice.

Clients bind credentials to the issuer (SEP-2352)

SEP-2352 advises clients that they should use separate credentials for each AS that they interact with. Having distinct credentials per issuer is good cryptographic hygiene, analogous to "don't reuse your passwords". That's a good bit of basic security to get nailed down in the MCP spec, but nothing we need to change in IdentityServer implementations.

Refresh Tokens (SEP-2207)

There is now guidance that allows for both pure OAuth 2.1 and OIDC-style refresh tokens.

Normally, a refresh token is requested using the offline_access scope defined in OIDC. Technically, a pure OAuth implementation allows the AS to decide if it wants to issue a refresh token based on its policy. Some pure OAuth implementations do this, usually based on whether the client has the refresh token grant type and whether policy allows it.

IdentityServer is an OpenID Connect implementation, so it relies on the offline_access scope. This SEP is clarifying that either approach is acceptable. IdentityServer implementations don't need to make any changes to follow it.

Scopes in Step-Up (SEP-2350)

SEP-2350 is largely guidance to the client for how it should go about requesting scopes, especially if there is step-up. There's no real new behavior in the AS to implement in IdentityServer. MCP does mention that some ASes have broader scopes (e.g. admin) that imply narrower scopes (e.g. read), and in the course of a step-up operation, the client might end up requesting both. It's up to the AS to maintain those relationships and emit a token with the correct scopes. If you're doing that in your IdentityServer implementation, you probably already needed it, so this isn't anything new.

Conclusion

I'm pleased to see that MCP is taking these steps to improve the security of the ecosystem. I sometimes worry that "move fast and break things" rules the day, but MCP's usage of OAuth has matured substantially over the past year, and it's reassuring to see that the security portions of the protocol are getting the attention they deserve. If you're an IdentityServer user or thinking of trying us out, these MCP updates will be straightforward to implement, and in fact largely are already built in and correct by default.

Stay tuned in this space for more information around AI security, the rapid pace of changes, and expert translation from security experts.

Related Articles