From 933772f687fc4bce9ee2d9eb9a71aa6f30d5e5a6 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 28 May 2026 09:07:16 -0400 Subject: [PATCH 1/5] Enhance sign-out guidance --- aspnetcore/blazor/security/index.md | 86 +++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index ce0a1f516a15..042b7eb08fe0 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -535,22 +535,98 @@ Two additional abstractions participate in managing authentication state: * ([reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Endpoints/src/DependencyInjection/ServerAuthenticationStateProvider.cs)): An used by the Blazor framework to obtain authentication state from the server. -* ([reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs)): A base class for services used by the Blazor framework to receive an authentication state from the host environment and revalidate it at regular intervals. +* ([reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs)): A base class for services used by the Blazor framework to receive an authentication state from the host environment and revalidate it at regular intervals, 30 minutes by default. [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] -In apps generated from the Blazor project template for .NET 8 or later, adjust the default 30 minute revalidation interval in `IdentityRevalidatingAuthenticationStateProvider`. Earlier than .NET 8, adjust the interval in `RevalidatingIdentityAuthenticationStateProvider`. The following example shortens the interval to 20 minutes: +### Authentication state management at sign out + +The default revalidation interval is 30 minutes for either ASP.NET Core Identity-based authentication or cookie-based authentication without Identity. Within the 30 minute window, it remains possible under certain sign-out conditions for a user to retain access to areas of the app that you might wish to prevent. + +To control the revalidation period and enforce a complete sign out process for users, begin by implementing a with a shorter . + +For an example implementation showing the default 30 minute interval, see the [`IdentityRevalidatingAuthenticationStateProvider` class (reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs) in the Blazor Web App project template. + +In the following example, the interval is set to five minutes: ```csharp -protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(20); +protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(5); ``` -### Authentication state management at sign out +Implementing with a short only revalidates the authentication state held by the current Blazor circuit, including across browser tabs. Returning `false` from flips the circuit's state to unauthenticated, so instances of / re-evaluate and redirect the user to sign in. However, returning `false` from doesn't affect the underlying authentication cookie. The next full navigation that occurs before the cookie expires or is invalidated recreates the principal from the cookie. When that happens, the cookie indicates a signed-in user to the , so the user appears signed in until the next tick fires. + +To control the revalidation interval of the underlying authentication cookie in apps that adopt ASP.NET Core Identity, see the following [Sign out for ASP.NET Core Identity](#sign-out-for-aspnet-core-identity) subsection for details. For apps that adopt cookie-based authentication without Identity, see the following [Sign out for cookie-based authentication](#sign-out-for-cookie-based-authentication) subsection. -Server-side Blazor persists user authentication state for the lifetime of the circuit, including across browser tabs. To proactively sign off a user across browser tabs when the user signs out on one tab, you must implement a ([reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs)) with a short . +The reference source for is located in [`RevalidatingServerAuthenticationStateProvider.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs). [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] +#### Sign out for ASP.NET Core Identity + +To proactively, completely sign a user off within less than the default 30 minute revalidation interval in apps that adopt ASP.NET Core Identity, use the guidance in this section. + +For Blazor apps that target .NET 8 or later, reduce the default 30 minute in the `IdentityRevalidatingAuthenticationStateProvider` class (`Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs`). If the app targets .NET earlier than .NET 8, reduce the interval in `RevalidatingIdentityAuthenticationStateProvider`. + +Whether or not the authentication cookie remains valid is checked by the *security stamp validator* (), which hooks into the event of an authentication cookie and queries the user datastore to determine if the user is still signed in. The security stamp validator's interval is governed by , which defaults to 30 minutes because validating users on every request triggers a database query on every request for every user. + +The following example shortens the default 30 minute interval to four minutes: + +```csharp +builder.Services.Configure( + o => o.ValidationInterval = TimeSpan.FromMinutes(4)); +``` + +The call interval is a tradeoff between hitting the user datastore too frequently and not often enough. Checking with a short interval can result in high demand on the user datastore and reduced app performance but with the benefit of more timely sign-outs. Checking with a long interval results in stale claims, which can make it appear that a user is still signed in if a full navigation recreates the principal from the authentication cookie before it naturally expires. + +To catch the next interval tick of the , set the to a period just inside the value set for . + +Custom C# code that's required to force a sign out on a user can call , which immediately invalidates existing cookies the next time they're checked. + +For more information, see . + +#### Sign out for cookie-based authentication + +To proactively, completely sign a user off within less than the default 30 minute revalidation interval in apps that adopt cookie-based authentication without ASP.NET Core Identity, use the guidance in this section. + +There are two approaches that you can take. The first approach is to wait for a revalidation check to occur and ensure cookie invalidation when the check is made. To adopt this approach, pair an implementation of with a shorter (default: 30 minutes) and a sign-out trigger. Implement the sign-out trigger using ***either*** of the following approaches: + +* Sign out on GET in the app's login page: + + ```csharp + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + ... + + if (HttpMethods.IsGet(HttpContext.Request.Method)) + { + await HttpContext.SignOutAsync( + CookieAuthenticationDefaults.AuthenticationScheme); + } + } + ``` + +* Call `NavigationManager.NavigateTo("/Account/Logout", forceLoad: true)` where you sign out users. Create an endpoint for `/Account/Logout` with a call to in the app's `Program` file, which in turn calls : + + ```csharp + app.MapPost("/Account/Logout", async (HttpContext context) => + { + await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + return TypedResults.LocalRedirect("/"); + }) + .RequireAuthorization(); + ``` + +The alternative second approach is aimed at first-request freshness without waiting for the next revalidation check. To adopt this approach, perform a user datastore authentication check in and call with . + +For more information, see the following sections of the *Use cookie authentication without ASP.NET Core Identity* article: + +* [Sign out](xref:security/authentication/cookie#sign-out) +* [React to back-end changes](xref:security/authentication/cookie#react-to-back-end-changes) + :::moniker range=">= aspnetcore-8.0" ### Temporary redirection URL validity duration From 9078ab07d842af75f6dcdf55a6469865887ee019 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 28 May 2026 09:10:45 -0400 Subject: [PATCH 2/5] Updates --- aspnetcore/blazor/security/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index 042b7eb08fe0..fd431fd1d36d 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -588,7 +588,7 @@ For more information, see with a shorter (default: 30 minutes) and a sign-out trigger. Implement the sign-out trigger using ***either*** of the following approaches: +There are two approaches that you can take. The first approach is to wait for a revalidation check to occur and ensure cookie invalidation when the check is made. To adopt this approach, pair an implementation of with a shorter (default: 30 minutes) and a sign-out trigger. Implement the sign-out trigger using ***either*** of the following approaches. * Sign out on GET in the app's login page: From ce2ffda2fe24589aa7e9a8af714e8e3c200b545d Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 28 May 2026 09:42:54 -0400 Subject: [PATCH 3/5] Updates --- aspnetcore/blazor/security/index.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index fd431fd1d36d..f11eacdd05bf 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -1,5 +1,6 @@ --- title: ASP.NET Core Blazor authentication and authorization +ai-usage: ai-assisted author: guardrex description: Learn about Blazor authentication and authorization scenarios. monikerRange: '>= aspnetcore-3.1' @@ -553,7 +554,10 @@ In the following example, the interval is set to five minutes: protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(5); ``` -Implementing with a short only revalidates the authentication state held by the current Blazor circuit, including across browser tabs. Returning `false` from flips the circuit's state to unauthenticated, so instances of / re-evaluate and redirect the user to sign in. However, returning `false` from doesn't affect the underlying authentication cookie. The next full navigation that occurs before the cookie expires or is invalidated recreates the principal from the cookie. When that happens, the cookie indicates a signed-in user to the , so the user appears signed in until the next tick fires. +Implementing with a short only revalidates the authentication state held by the current Blazor circuit. Returning `false` from flips the circuit's state to unauthenticated, so instances of / re-evaluate and redirect the user to sign in. However, returning `false` from doesn't affect the underlying authentication cookie. The next full navigation that occurs before the cookie expires or is invalidated recreates the principal from the cookie. When that happens, the cookie indicates a signed-in user to the , so the user appears signed in until the next tick fires. + +> [!NOTE] +> Each browser tab requires a separate circuit, so additional tabs opened by a user don't observe an authentication state change until their circuits revalidate. However, the approach in this section sets the revalidation interval for all of the tabs opened by a user. To control the revalidation interval of the underlying authentication cookie in apps that adopt ASP.NET Core Identity, see the following [Sign out for ASP.NET Core Identity](#sign-out-for-aspnet-core-identity) subsection for details. For apps that adopt cookie-based authentication without Identity, see the following [Sign out for cookie-based authentication](#sign-out-for-cookie-based-authentication) subsection. @@ -590,7 +594,7 @@ To proactively, completely sign a user off within less than the default 30 minut There are two approaches that you can take. The first approach is to wait for a revalidation check to occur and ensure cookie invalidation when the check is made. To adopt this approach, pair an implementation of with a shorter (default: 30 minutes) and a sign-out trigger. Implement the sign-out trigger using ***either*** of the following approaches. -* Sign out on GET in the app's login page: +* Sign out on GET in the app's login page. This approach is only valid for static SSR because is `null` during interactive rendering: ```csharp [CascadingParameter] @@ -611,7 +615,7 @@ There are two approaches that you can take. The first approach is to wait for a * Call `NavigationManager.NavigateTo("/Account/Logout", forceLoad: true)` where you sign out users. Create an endpoint for `/Account/Logout` with a call to in the app's `Program` file, which in turn calls : ```csharp - app.MapPost("/Account/Logout", async (HttpContext context) => + app.MapGet("/Account/Logout", async (HttpContext context) => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); From baa34d29c08c7b182a225350c7a8def03e884ffe Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Thu, 28 May 2026 17:01:51 -0400 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Stephen Halter --- aspnetcore/blazor/security/index.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index f11eacdd05bf..2d58bf9e29b0 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -559,15 +559,12 @@ Implementing [!NOTE] > Each browser tab requires a separate circuit, so additional tabs opened by a user don't observe an authentication state change until their circuits revalidate. However, the approach in this section sets the revalidation interval for all of the tabs opened by a user. -To control the revalidation interval of the underlying authentication cookie in apps that adopt ASP.NET Core Identity, see the following [Sign out for ASP.NET Core Identity](#sign-out-for-aspnet-core-identity) subsection for details. For apps that adopt cookie-based authentication without Identity, see the following [Sign out for cookie-based authentication](#sign-out-for-cookie-based-authentication) subsection. +To control the revalidation interval in apps that adopt ASP.NET Core Identity with cookie authentication, see the following [Sign out for ASP.NET Core Identity](#sign-out-for-aspnet-core-identity) subsection for details. For apps that adopt cookie-based authentication without Identity, see the following [Sign out for cookie-based authentication](#sign-out-for-cookie-based-authentication) subsection. -The reference source for is located in [`RevalidatingServerAuthenticationStateProvider.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs). - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] #### Sign out for ASP.NET Core Identity -To proactively, completely sign a user off within less than the default 30 minute revalidation interval in apps that adopt ASP.NET Core Identity, use the guidance in this section. +To force a complete sign-out within less than the default 30-minute revalidation interval in apps that adopt ASP.NET Core Identity, use the guidance in this section. For Blazor apps that target .NET 8 or later, reduce the default 30 minute in the `IdentityRevalidatingAuthenticationStateProvider` class (`Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs`). If the app targets .NET earlier than .NET 8, reduce the interval in `RevalidatingIdentityAuthenticationStateProvider`. From 2fabf7bf6f3073bb432f49ca2c150a1bc0ea6561 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 28 May 2026 17:06:10 -0400 Subject: [PATCH 5/5] Updates --- aspnetcore/blazor/security/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index 2d58bf9e29b0..e6b7add3fb69 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -542,11 +542,11 @@ Two additional abstractions participate in managing authentication state: ### Authentication state management at sign out -The default revalidation interval is 30 minutes for either ASP.NET Core Identity-based authentication or cookie-based authentication without Identity. Within the 30 minute window, it remains possible under certain sign-out conditions for a user to retain access to areas of the app that you might wish to prevent. +The default revalidation interval is 30 minutes for either ASP.NET Core Identity-based authentication or cookie-based authentication without Identity. Within the 30-minute window, it remains possible under certain sign-out conditions for a user to retain access to areas of the app that you might wish to prevent. To control the revalidation period and enforce a complete sign out process for users, begin by implementing a with a shorter . -For an example implementation showing the default 30 minute interval, see the [`IdentityRevalidatingAuthenticationStateProvider` class (reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs) in the Blazor Web App project template. +For an example implementation showing the default 30-minute interval, see the [`IdentityRevalidatingAuthenticationStateProvider` class (reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs) in the Blazor Web App project template. In the following example, the interval is set to five minutes: @@ -566,11 +566,11 @@ To control the revalidation interval in apps that adopt ASP.NET Core Identity wi To force a complete sign-out within less than the default 30-minute revalidation interval in apps that adopt ASP.NET Core Identity, use the guidance in this section. -For Blazor apps that target .NET 8 or later, reduce the default 30 minute in the `IdentityRevalidatingAuthenticationStateProvider` class (`Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs`). If the app targets .NET earlier than .NET 8, reduce the interval in `RevalidatingIdentityAuthenticationStateProvider`. +For Blazor apps that target .NET 8 or later, reduce the default 30-minute in the `IdentityRevalidatingAuthenticationStateProvider` class (`Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs`). If the app targets .NET earlier than .NET 8, reduce the interval in `RevalidatingIdentityAuthenticationStateProvider`. Whether or not the authentication cookie remains valid is checked by the *security stamp validator* (), which hooks into the event of an authentication cookie and queries the user datastore to determine if the user is still signed in. The security stamp validator's interval is governed by , which defaults to 30 minutes because validating users on every request triggers a database query on every request for every user. -The following example shortens the default 30 minute interval to four minutes: +The following example shortens the default 30-minute interval to four minutes: ```csharp builder.Services.Configure( @@ -587,7 +587,7 @@ For more information, see with a shorter (default: 30 minutes) and a sign-out trigger. Implement the sign-out trigger using ***either*** of the following approaches.