This repository has been archived by the owner on Jan 24, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a3cc773
commit d37bd36
Showing
28 changed files
with
931 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# MacOs | ||
.DS_Store | ||
|
||
# Rider | ||
.idea | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// See LICENSE in the project root for license information. | ||
|
||
namespace Duende.Bff.Blazor.Client; | ||
|
||
public class AntiforgeryHandler : DelegatingHandler | ||
{ | ||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
request.Headers.Add("X-CSRF", "1"); | ||
return base.SendAsync(request, cancellationToken); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// See LICENSE in the project root for license information. | ||
|
||
namespace Duende.Bff.Blazor.Client; | ||
|
||
/// <summary> | ||
/// Options for Blazor BFF | ||
/// </summary> | ||
public class BffBlazorOptions | ||
{ | ||
/// <summary> | ||
/// The base path to use for remote APIs. | ||
/// </summary> | ||
public string RemoteApiPath { get; set; } = "remote-apis/"; | ||
|
||
/// <summary> | ||
/// The base address to use for remote APIs. If unset (the default), the | ||
/// blazor hosting environment's base address is used. | ||
/// </summary> | ||
public string? RemoteApiBaseAddress { get; set; } = null; | ||
|
||
/// <summary> | ||
/// The delay, in milliseconds, before the AuthenticationStateProvider | ||
/// will start polling the /bff/user endpoint. Defaults to 1000 ms. | ||
/// </summary> | ||
public int StateProviderPollingDelay { get; set; } = 1000; | ||
|
||
/// <summary> | ||
/// The delay, in milliseconds, between polling requests by the | ||
/// AuthenticationStateProvider to the /bff/user endpoint. Defaults to | ||
/// 5000 ms. | ||
/// </summary> | ||
public int StateProviderPollingInterval { get; set; } = 5000; | ||
} |
147 changes: 147 additions & 0 deletions
147
src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// See LICENSE in the project root for license information. | ||
|
||
using System.Net.Http.Json; | ||
using System.Security.Claims; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.AspNetCore.Components.Authorization; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Duende.Bff.Blazor.Client; | ||
|
||
public class BffClientAuthenticationStateProvider : AuthenticationStateProvider | ||
{ | ||
private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60); | ||
|
||
private readonly HttpClient _client; | ||
private readonly ILogger<BffClientAuthenticationStateProvider> _logger; | ||
private readonly BffBlazorOptions _options; | ||
|
||
private DateTimeOffset _userLastCheck = DateTimeOffset.MinValue; | ||
private ClaimsPrincipal _cachedUser = new(new ClaimsIdentity()); | ||
|
||
/// <summary> | ||
/// An <see cref="AuthenticationStateProvider"/> intended for use in | ||
/// Blazor WASM. It polls the /bff/user endpoint to monitor session | ||
/// state. | ||
/// </summary> | ||
public BffClientAuthenticationStateProvider( | ||
PersistentComponentState state, | ||
IHttpClientFactory factory, | ||
IOptions<BffBlazorOptions> options, | ||
ILogger<BffClientAuthenticationStateProvider> logger) | ||
{ | ||
_client = factory.CreateClient("BffAuthenticationStateProvider"); | ||
_logger = logger; | ||
_cachedUser = GetPersistedUser(state); | ||
if (_cachedUser.Identity?.IsAuthenticated == true) | ||
{ | ||
_userLastCheck = DateTimeOffset.Now; | ||
} | ||
|
||
_options = options.Value; | ||
} | ||
|
||
public override async Task<AuthenticationState> GetAuthenticationStateAsync() | ||
{ | ||
var user = await GetUser(); | ||
var state = new AuthenticationState(user); | ||
|
||
// Periodically | ||
if (user.Identity is { IsAuthenticated: true }) | ||
{ | ||
_logger.LogInformation("starting background check.."); | ||
Timer? timer = null; | ||
|
||
timer = new Timer(async _ => | ||
{ | ||
var currentUser = await GetUser(false); | ||
// Always notify that auth state has changed, because the user | ||
// management claims (usually) change over time. | ||
// | ||
// Future TODO - Someday we may want an extensibility point. If the | ||
// user management claims have been customized, then auth state | ||
// wouldn't always change. In that case, we'd want to only fire | ||
// if the user actually had changed. | ||
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(currentUser))); | ||
|
||
if (currentUser!.Identity!.IsAuthenticated == false) | ||
{ | ||
_logger.LogInformation("user logged out"); | ||
|
||
if (timer != null) | ||
{ | ||
await timer.DisposeAsync(); | ||
} | ||
} | ||
}, null, _options.StateProviderPollingDelay, _options.StateProviderPollingInterval); | ||
} | ||
|
||
return state; | ||
} | ||
|
||
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = true) | ||
{ | ||
var now = DateTimeOffset.Now; | ||
if (useCache && now < _userLastCheck + UserCacheRefreshInterval) | ||
{ | ||
_logger.LogDebug("Taking user from cache"); | ||
return _cachedUser; | ||
} | ||
|
||
_logger.LogDebug("Fetching user"); | ||
_cachedUser = await FetchUser(); | ||
_userLastCheck = now; | ||
|
||
return _cachedUser; | ||
} | ||
|
||
// TODO - Consider using ClaimLite instead here | ||
record ClaimRecord(string Type, object Value); | ||
|
||
private async Task<ClaimsPrincipal> FetchUser() | ||
{ | ||
try | ||
{ | ||
_logger.LogInformation("Fetching user information."); | ||
var response = await _client.GetAsync("bff/user?slide=false"); | ||
response.EnsureSuccessStatusCode(); | ||
var claims = await response.Content.ReadFromJsonAsync<List<ClaimRecord>>(); | ||
|
||
var identity = new ClaimsIdentity( | ||
nameof(BffClientAuthenticationStateProvider), | ||
"name", | ||
"role"); | ||
|
||
if (claims != null) | ||
{ | ||
foreach (var claim in claims) | ||
{ | ||
identity.AddClaim(new Claim(claim.Type, claim.Value.ToString() ?? "no value")); | ||
} | ||
} | ||
|
||
return new ClaimsPrincipal(identity); | ||
} | ||
catch (Exception ex) | ||
{ | ||
_logger.LogWarning(ex, "Fetching user failed."); | ||
} | ||
|
||
return new ClaimsPrincipal(new ClaimsIdentity()); | ||
} | ||
|
||
private ClaimsPrincipal GetPersistedUser(PersistentComponentState state) | ||
{ | ||
if (!state.TryTakeFromJson<ClaimsPrincipalLite>(nameof(ClaimsPrincipalLite), out var lite) || lite is null) | ||
{ | ||
_logger.LogDebug("Failed to load persisted user."); | ||
return new ClaimsPrincipal(new ClaimsIdentity()); | ||
} | ||
|
||
_logger.LogDebug("Persisted user loaded."); | ||
|
||
return lite.ToClaimsPrincipal(); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" /> | ||
<PackageReference Include="Microsoft.Extensions.Http" /> | ||
<!-- Explicitly taking this version so that we don't pull in vulnerable old versions. --> | ||
<PackageReference Include="System.Text.Json" Version="8.0.4"/> | ||
|
||
<ProjectReference Include="../Duende.Bff.Shared/Duende.Bff.Shared.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.