The role of AuthenticationProperties in ASP.NET Core
When working with user authentication in ASP.NET Core, you may encounter situations where you need to pass additional information through the authentication process. For example, you might want to track specific user actions, ensure they are redirected to a particular page after logging in, or pass custom parameters to an identity provider.
The AuthenticationProperties
class provides an elegant solution for these scenarios. It allows you to carry state through call sites within a specific request, maintain state throughout the authentication process, or pass additional parameters to the identity provider you are using.
In this post, we’ll see how to use the AuthenticationProperties
class effectively in your ASP.NET Core applications and explore some OpenID Connect-specific options you can use in your apps.
What is AuthenticationProperties?
The AuthenticationProperties
class in ASP.NET Core is a simple yet powerful way to pass additional state or metadata during the authentication process.
While it has several properties like AllowRefresh
, ExpiresUtc
, IssuedUtc
, IsPersistent,
and RedirectUri
that reflect various characteristics of an authentication session, these values are backed by two dictionaries:
Items
- Contains state values about the authentication session and is carried throughout the authentication process.Parameters
- Contains parameters passed to the authentication handler and used for flowing data between call sites in the same request.
Let’s look at some examples of where and how they can be used!
Using AuthenticationProperties.Items in your application
The Items
dictionary in AuthenticationProperties
is designed to store state values that persist throughout the authentication process. This can be useful for scenarios where you need to track custom data related to the authentication session, such as user-specific metadata or temporary flags.
For example, suppose you are building a weather app, and the user selects a weather location before signing in. You can store this information in the Items
dictionary and retrieve it later after the user logs in:
var items = new Dictionary<string, string?>
{
["weather_location"] = "Antwerp, Belgium"
};
return Challenge(
properties: new AuthenticationProperties(items)
{
RedirectUri = "/home"
},
authenticationSchemes: "oidc");
In this example, the Challenge
method initiates the authentication process using the “oidc” scheme. A custom key/value pair ("weather_location": "Antwerp, Belgium"
) is added to the Items
dictionary, which will be carried along through the authentication flow.
Later, after the user logs in, you can access this data by inspecting the AuthenticationProperties
associated with the current user, for example, in Razor Pages. Note that the method name AuthenticateAsync
may be misleading: it is used to retrieve information for the current user and not to trigger authentication (which would be done using the ChallengeAsync
method).
public class WeatherModel : PageModel
{
public string WeatherLocation { get; private set; }
public async Task<IActionResult> OnGet()
{
var authResult = await HttpContext.AuthenticateAsync();
var authProperties = authResult?.Properties;
if (authProperties?.Items.TryGetValue("weather_location", out var weatherLocation) == true)
{
Weather = RetrieveWeatherForLocationAsync(weatherLocation);
}
return Page();
}
}
Nice! This approach allows you to transfer the user’s selected weather location through the authentication process and use it in your Razor Pages to enhance the user experience in your weather app.
Note that while the Items
dictionary is a convenient way to pass small amounts of data through the authentication process, it is not intended to serve as a session storage mechanism. The data stored in the Items
dictionary is often roundtripped to the identity provider, which in may have a limit on the size of this parameter. If you need to store and carry more than one or a few key-value pairs through the authentication flow, consider using other storage mechanisms to keep the state, such as sessions or TempData
.
External identity providers will not be able to see the contents of the Items
dictionary. While data is serialized and typically added to the state
query string parameter, it is encrypted using ASP.NET Core Data Protection and only readable by your own application. We recommend always configuring Data Protection in your client applications!
The state
parameter may be part of the query string sent to the authorization endpoint, but if you are using Pushed Authorization Requests (PAR) it will be sent to the identity provider using a back channel to prevent authorization parameters being seen or tampered with - even if they are encrypted by default.
Using AuthenticationProperties.Parameters in your application
The Parameters
dictionary in AuthenticationProperties
is used to pass additional data to the authentication handler during the authentication process. This is particularly useful when you need to send custom parameters to an external identity provider or modify the behavior of the authentication flow.
For example, suppose you are integrating with an OpenID Connect provider and want to pre-fill the username field on the login page by passing a login_hint
parameter. You can achieve this by adding the parameter to the Parameters
dictionary:
var parameters = new Dictionary<string, object?>
{
["login_hint"] = "user@example.com"
};
return Challenge(
properties: new AuthenticationProperties
{
RedirectUri = "/profile",
Parameters = parameters
},
authenticationSchemes: "oidc");
In this example, the Challenge
method initiates the authentication process using the “oidc” scheme. The login_hint
parameter is added to the Parameters
dictionary, which is then passed along to any authentication events or handlers in your ASP.NET Core pipeline. For example, you could consume the login_hint
parameter in the OnRedirectToIdentityProvider
of your OpenIdConnectOptions
:
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "AcmeCorp.WeatherApp";
})
.AddOpenIdConnect(options =>
{
// ...
options.Events.OnRedirectToIdentityProvider = async context =>
{
if (context.Properties.Parameters.TryGetValue("login_hint", out var loginHint)
{
Logger.LogInformation("Setting login hint to {LoginHint}", loginHint);
}
await Task.FromResult(0);
};
// ...
});
Note that the Parameters
dictionary is available in the event callback, but changes you make to this collection will not be reflected in the redirect to the identity provider. As we’ll see later in this post, you’ll need to use the ProtocolMessage
property on the context
object here to update any query parameters sent to the identity provider.
The identity provider can use the login_hint
parameter to pre-fill the username field on the login page, improving the user experience by reducing the information the user needs to enter manually. If you’re using Duende IdentityServer, you’ll find the UI templates come with a login page in which the login hint is consumed from the authorization request parameters:
private async Task BuildModelAsync(string? returnUrl)
{
// ...
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
{
// ...
Input.Username = context.LoginHint;
// ...
}
// ...
}
The authorization context
here has convenience properties for many known parameters like tenant (in the acr
parameter), login_hint
, prompt modes, and more. Custom parameters can be accessed using the Parameters
property:
var customParameter = context.Parameters["custom-parameter"];
Keep in mind that the Parameters
dictionary is not encrypted or protected like the Items
dictionary. It is intended to pass data between your application and the authentication handler, and its contents may be visible to external systems. Use it only for non-sensitive data or data required by the identity provider.
Note that while the Parameters
dictionary is sent to the identity provider for the OpenIdConnectHandler
, this may not be true for all authentication handlers available in ASP.NET Core. An example is the GoogleHandler
, which only sends specific well-known parameters such as login_hint
, prompt
, and approval_prompt
.
Providing additional information to an OpenID Connect identity provider
In the previous section, we saw how the AuthenticationProperties.Parameters
dictionary can flow data between call sites in the ASP.NET Core authentication pipeline. There are two more ways to do this, at least in the case of the OpenIdConnectHandler
:
- Using the
ProtocolMessage.Parameters
dictionary in an event callback likeOnRedirectToIdentityProvider
- Using the
AdditionalAuthorizationParameters
dictionary inOpenIdConnectOptions
(in .NET 9+)
As a general rule of thumb, I’d recommend using AdditionalAuthorizationParameters
when a static parameter value has to be added when redirecting to the identity provider, and ProtocolMessage.Parameters
when the parameter’s value must differ based on custom logic.
Returning to the previous section example, you could consume the login_hint
parameter in the OnRedirectToIdentityProvider
of your OpenIdConnectOptions
, and update its value in ProtocolMessage.Parameters
:
.AddOpenIdConnect(options =>
{
// ...
// Add application_type query string parameter when redirecting to the identity provider
options.AdditionalAuthorizationParameters.Add("application_type", "demo");
// ...
options.Events.OnRedirectToIdentityProvider = async context =>
{
// Append #external if login hint domain is not example.com
if (context.Properties.Parameters.TryGetValue("login_hint", out var loginHint)
&& loginHint != null
&& !loginHint.ToString()!.EndsWith("@example.com"))
{
context.ProtocolMessage.Parameters["login_hint"] = loginHint + "#external";
}
await Task.FromResult(0);
};
// ...
});
Conclusion
In this post, we explored the AuthenticationProperties
class in ASP.NET Core and its two key dictionaries: Items
and Parameters
. We saw how Items
can carry state throughout the authentication process while Parameters
allow passing additional data to authentication handlers or identity providers. We also discussed OpenID Connect-specific options like ProtocolMessage.Parameters
and AdditionalAuthorizationParameters
for customizing authentication flows.
Using these features, you can build more flexible authentication flows that support your application’s needs.
What are your thoughts on using AuthenticationProperties
in your applications? Let us know in the comments below!