In the world of web application security, OpenID Connect plays a key role in streamlining authentication processes. But what makes it really tick? In this blog post, we dive deep into two critical security features of OpenID Connect – the state and nonce parameters – and how they are used in ASP.NET Core.

Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core

In the world of web application security, OpenID Connect plays a key role in streamlining authentication processes. But what makes it really tick? In this blog post, we dive deep into two critical security features of OpenID Connect – the state and nonce parameters – and how they are used in ASP.NET Core.

This simplified diagram tries to show how the state and nonce are used when a user authenticates using OpenID Connect:

In the image above, the client should verify that the returned state value matches the expected value and that the nonce inside the ID-token also matches the expected value.

Challenging the user

In ASP.NET Core, when a user attempts to access a secured part of your application, the OpenID Connect handler initiates a challenge request. This can happen automatically via the authorization middleware or manually through the ChallengeAsync method:

public async Task Login()
    if (User.Identity.IsAuthenticated == false)
        await HttpContext.ChallengeAsync();


This challenge triggers a redirect to the authorization server, such as Duende IdentityServer, with a URL crafted with various parameters, including the client_id, redirect_uri, response_type, and, notably, the state and nonce parameters.

The redirect URL can look something like this:


The exact content depends on your OpenID-Connect middleware configuration.

The state parameter

The state parameter is a crucial aspect of an OpenID Connect authorization request. But, what’s its main purpose?

In OpenID Connect, the state parameter serves as an opaque value created by the client. Its primary function is maintaining the state between the initial request and the callback, acting as a shield against cross-site request forgery attacks during the authentication process.

While some implementations keep the state as a random value, that is verified during the callback. Other implementations injecting “data” into the state parameter. By doing this, the client doesn’t have to remember this information elsewhere, making the client stateless.

Looking into the state parameter 

The OIDC handler in ASP.NET Core stores data in the state parameter, which is encrypted and secured using the Data Protection API.

However, we can “bypass” this encryption and issue an unencrypted state parameter. To do this, we can add our own transparent data protector and implement it like this:

					public class MyDataProtector : IDataProtector
    public IDataProtector CreateProtector(string purpose)
        return new MyDataProtector();

    public byte[] Protect(byte[] plaintext)
        return plaintext;

    public byte[] Unprotect(byte[] protectedData)
        return protectedData;

Then, we can tell the OpenIDConnect handler to replace the default encryption protector with this one instead:
					}).AddOpenIdConnect("oidc", o =>
    o.StateDataFormat = new PropertiesDataFormat(new MyDataProtector());

Adding this allows us to peek inside the state parameter:

If we decode the state parameter using a tool like base64 decode , then we get a state that looks like this:

(The non-printable characters have been replaced with a dot).
The above can be a bit hard to read, a cleaned-up version looks like this:
					[0]: {[.redirect, /User/Login?]}
    [1]: {[code_verifier, GHIPb2bFHs_X7FF4fJmZd0JGPWnnAyyeUilQrg2164M]}
    [2]: {[.xsrf, -31ZKLoCwNUTrwDA5_HtddLOmKUm5d3Jb0-x_vfJmQY]}
    [3]: {[OpenIdConnect.Code.RedirectUri, https://localhost:5001/signin-oidc]}


Can I add custom properties here?

Yes, if you provide an instance of the AuthenticationProperties with the challenge, like this:

public async Task Login()
    if (User.Identity.IsAuthenticated == false)
        var properties = new AuthenticationProperties()
            RedirectUri = "/",
            Items =
                { "IpAddress", "" },
                { "ComputerName", "MyComputer" },
                { "ApiKey", "Summer2023" },
                { "Language", "English" }

        await HttpContext.ChallengeAsync(properties);

Then you might end up with a state parameter that looks like this:

If you base64 decode it and then clean it up, you will get the following information:
					[0]: {[.redirect, /]}
    [1]: {[IpAddress,]}
    [2]: {[ComputerName, MyComputer]}
    [3]: {[ApiKey, Summer2023]}
    [4]: {[Language, English]}
    [5]: {[code_verifier, iTl5Rw3bz5Jv16otzKHlzGODFJ73nRzw4Rl48fHxDaE]}
    [6]: {[.xsrf, XNwt_wz-29iTkFbIW7flXcpvjezqulIVbKyjT3Yb3gM]}
    [7]: {[OpenIdConnect.Code.RedirectUri, https://localhost:5001/signin-oidc]}

The AuthenticationProperties that you passed to the initial challenge, will later be available in the ClaimsPrincipal user object after the user is signed in.

The nonce parameter

The nonce parameter in OpenID Connect is crucial for associating a client session with the ID-Token and it is used for mitigating replay attacks.

In ASP.NET Core, it’s generated by the GenerateNonce method, as shown below:

					public virtual string GenerateNonce()
   string nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString() + 
    if (RequireTimeStampInNonce)
        return DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture) + "." + nonce;

    return nonce;


The method is found in the OpenIdConnectProtocolValidator class.

The nonce generated by this method is a string that consists of a timestamp and a random value; it can look like this:


How is the nonce parameter handled in ASP.NET Core?

Upon challenging the user, a nonce is generated and stored as a cookie, as shown below:

expires=Thu, 16 Nov 2023 14:42:08 GMT; path=/signin-oidc; secure; samesite=none; httponly

The cookie name is “.AspNetCore.OpenIdConnect.[Nonce]” and the interesting thing here is that the cookie name contains the nonce value. The nonce here is also protected using the Data Protection API.

Similar to what we did before, we can introduce the transparent protector by setting the StringDataFormat property.

					}).AddOpenIdConnect("oidc", o =>
    o.StateDataFormat = new PropertiesDataFormat(new MyDataProtector());
    o.StringDataFormat = new SecureDataFormat<string>(new StringSerializer(),
    new MyDataProtector());


Adding this will make the handler store the nonce in the cookie in unprotected form. However, this is not useful, as there’s little interesting information to see in the nonce cookie.

Besides storing the nonce in the cookie, it is also included in its raw form in the authentication request to the authorization server:




In this blog post, we have explored what is inside the encrypted state parameter that is passed to the authorization server when the user is challenged. We also saw that we can pass custom AuthenticationProperties to the challenge operation, and these properties will later end up in the ClaimsPrincipal user object.

We also explored the nonce, a random string that we store securely in a cookie and pass in its raw form to the authorization server. The nonce ties the ID-token to the initial request made to the authorization server. With the nonce, the client knows the token is generated for itself, and it won’t consume a token injected by a malicious party.

Feedback, comments, found any bugs?

Let me know if you have any feedback, anything I missed, or any bugs/typos. You can find my contact details here.

About the author

Hi, I’m Tore! I have been fascinated by computers since I unpacked my first Commodore VIC-20. Today, I enjoy providing freelance development and developer training services, focusing on ASP.NET Core, IdentityServer, OpenID Connect, Architecture, and Web Security. You can connect with me on LinkedIn and Twitter, and you can find out more about me and my services here, as well as my courses for developers, including my course, Introduction to IdentityServer and OpenID-Connect.

Share This Story
Share on linkedin
Share on twitter
Share on facebook
Share on pinterest
Share on email

About me

My name is Tore Nestenius and I’m a trainer and senior software developer focusing on Architecture, Security and Identity, .NET, C#, Backend, and Cloud, among other things.

Do You Want Tore To Be Your Mentor?