Add authentication revalidation logic to IndividualLocalAuth server-side Blazor template. Implements #10698 (#11548)

This commit is contained in:
Steve Sanderson 2019-06-28 09:12:59 +01:00 committed by GitHub
parent 33849f3381
commit d1146d043f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 0 deletions

View File

@ -0,0 +1,98 @@
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace RazorComponentsWeb_CSharp.Areas.Identity
{
/// <summary>
/// An <see cref="AuthenticationStateProvider"/> service that revalidates the
/// authentication state at regular intervals. If a signed-in user's security
/// stamp changes, this revalidation mechanism will sign the user out.
/// </summary>
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
public class RevalidatingAuthenticationStateProvider<TUser>
: AuthenticationStateProvider, IDisposable where TUser : class
{
private readonly static TimeSpan RevalidationInterval = TimeSpan.FromMinutes(30);
private readonly CancellationTokenSource _loopCancellationTokenSource = new CancellationTokenSource();
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger _logger;
private Task<AuthenticationState> _currentAuthenticationStateTask;
public RevalidatingAuthenticationStateProvider(
IServiceScopeFactory scopeFactory,
SignInManager<TUser> circuitScopeSignInManager,
ILogger<RevalidatingAuthenticationStateProvider<TUser>> logger)
{
var initialUser = circuitScopeSignInManager.Context.User;
_currentAuthenticationStateTask = Task.FromResult(new AuthenticationState(initialUser));
_scopeFactory = scopeFactory;
_logger = logger;
if (initialUser.Identity.IsAuthenticated)
{
_ = RevalidationLoop();
}
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=> _currentAuthenticationStateTask;
private async Task RevalidationLoop()
{
var cancellationToken = _loopCancellationTokenSource.Token;
while (!cancellationToken.IsCancellationRequested)
{
try
{
await Task.Delay(RevalidationInterval, cancellationToken);
}
catch (TaskCanceledException)
{
break;
}
var isValid = await CheckIfAuthenticationStateIsValidAsync();
if (!isValid)
{
// Force sign-out. Also stop the revalidation loop, because the user can
// only sign back in by starting a new connection.
var anonymousUser = new ClaimsPrincipal();
_currentAuthenticationStateTask = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(_currentAuthenticationStateTask);
_loopCancellationTokenSource.Cancel();
}
}
}
private async Task<bool> CheckIfAuthenticationStateIsValidAsync()
{
try
{
// Get the sign-in manager from a new scope to ensure it fetches fresh data
using (var scope = _scopeFactory.CreateScope())
{
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<TUser>>();
var authenticationState = await _currentAuthenticationStateTask;
var validatedUser = await signInManager.ValidateSecurityStampAsync(authenticationState.User);
return validatedUser != null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while revalidating authentication state");
return false;
}
}
void IDisposable.Dispose()
=> _loopCancellationTokenSource.Cancel();
}
}

View File

@ -36,6 +36,9 @@ using Microsoft.Extensions.Hosting;
#if(MultiOrgAuth)
using Microsoft.IdentityModel.Tokens;
#endif
#if (IndividualLocalAuth)
using RazorComponentsWeb_CSharp.Areas.Identity;
#endif
using RazorComponentsWeb_CSharp.Data;
namespace RazorComponentsWeb_CSharp
@ -64,6 +67,8 @@ namespace RazorComponentsWeb_CSharp
#endif
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddScoped<AuthenticationStateProvider, RevalidatingAuthenticationStateProvider<IdentityUser>>();
#elif (OrganizationalAuth)
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));

View File

@ -894,6 +894,7 @@
"_Imports.razor",
"Areas/Identity/Pages/Account/LogOut.cshtml",
"Areas/Identity/Pages/Shared/_LoginPartial.cshtml",
"Areas/Identity/RevalidatingAuthenticationStateProvider.cs",
"Data/ApplicationDbContext.cs",
"Data/WeatherForecast.cs",
"Data/WeatherForecastService.cs",