diff --git a/Directory.Build.targets b/Directory.Build.targets
index de1c12d..f6ea513 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -17,7 +17,9 @@
-
+
+
+
@@ -30,6 +32,7 @@
+
diff --git a/samples/Blazor/PerComponent/PerComponent.Client/Components/Auto.razor b/samples/Blazor/PerComponent/PerComponent.Client/Components/Auto.razor
index c8cf14b..ccf4a55 100644
--- a/samples/Blazor/PerComponent/PerComponent.Client/Components/Auto.razor
+++ b/samples/Blazor/PerComponent/PerComponent.Client/Components/Auto.razor
@@ -1,8 +1,3 @@
@rendermode InteractiveAuto
-
-@code {
- [CascadingParameter]
- private Task? authenticationState { get; set; }
-}
diff --git a/samples/Blazor/PerComponent/PerComponent.Client/Components/CallApi.razor b/samples/Blazor/PerComponent/PerComponent.Client/Components/CallApi.razor
index 75ac46a..476b822 100644
--- a/samples/Blazor/PerComponent/PerComponent.Client/Components/CallApi.razor
+++ b/samples/Blazor/PerComponent/PerComponent.Client/Components/CallApi.razor
@@ -37,7 +37,9 @@
protected async Task CallApiAsync()
{
+ DisableUi = true;
apiResult = await Http.GetFromJsonAsync("user-token");
+ DisableUi = false;
}
protected override void OnAfterRender(bool firstRender)
diff --git a/samples/Blazor/PerComponent/PerComponent.Client/Components/Wasm.razor b/samples/Blazor/PerComponent/PerComponent.Client/Components/Wasm.razor
index f44d929..1e556d9 100644
--- a/samples/Blazor/PerComponent/PerComponent.Client/Components/Wasm.razor
+++ b/samples/Blazor/PerComponent/PerComponent.Client/Components/Wasm.razor
@@ -1,12 +1,3 @@
@rendermode InteractiveWebAssembly
-
-@* TODO - This cascading auth state gets the auth state provider running. But
-actually, we don't need the auth state provider to be running. I thought it was
-weird that it wasn't, so this forces it to do so, but actually we're not doing
-anything with the state changes. *@
-@code {
- [CascadingParameter]
- private Task? authenticationState { get; set; }
-}
diff --git a/samples/Blazor/PerComponent/PerComponent.Client/PerComponent.Client.csproj b/samples/Blazor/PerComponent/PerComponent.Client/PerComponent.Client.csproj
index 0f7e952..01cbcc0 100644
--- a/samples/Blazor/PerComponent/PerComponent.Client/PerComponent.Client.csproj
+++ b/samples/Blazor/PerComponent/PerComponent.Client/PerComponent.Client.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/samples/Blazor/PerComponent/PerComponent.Client/Program.cs b/samples/Blazor/PerComponent/PerComponent.Client/Program.cs
index 358897f..cf9f8b9 100644
--- a/samples/Blazor/PerComponent/PerComponent.Client/Program.cs
+++ b/samples/Blazor/PerComponent/PerComponent.Client/Program.cs
@@ -7,7 +7,9 @@
builder.Services.AddScoped();
-builder.Services.AddBff();
-builder.Services.AddRemoteApiHttpClient("callApi");
+builder.Services
+ .AddBffBlazorClient()
+ .AddCascadingAuthenticationState()
+ .AddRemoteApiHttpClient("callApi");
await builder.Build().RunAsync();
diff --git a/samples/Blazor/PerComponent/PerComponent/PerComponent.csproj b/samples/Blazor/PerComponent/PerComponent/PerComponent.csproj
index d60e950..2ff5737 100644
--- a/samples/Blazor/PerComponent/PerComponent/PerComponent.csproj
+++ b/samples/Blazor/PerComponent/PerComponent/PerComponent.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/samples/Blazor/PerComponent/PerComponent/Program.cs b/samples/Blazor/PerComponent/PerComponent/Program.cs
index bc3ddcf..f10bb9c 100644
--- a/samples/Blazor/PerComponent/PerComponent/Program.cs
+++ b/samples/Blazor/PerComponent/PerComponent/Program.cs
@@ -7,20 +7,21 @@
var builder = WebApplication.CreateBuilder(args);
-// Add services to the container.
+// BFF setup for blazor
builder.Services.AddBff()
.AddServerSideSessions()
.AddBlazorServer()
.AddRemoteApis();
-
-builder.Services.AddCascadingAuthenticationState();
-
-builder.Services.AddScoped();
builder.Services.AddUserAccessTokenHttpClient("callApi", configureClient: client => client.BaseAddress = new Uri("https://localhost:5010/"));
+// General blazor services
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
+builder.Services.AddCascadingAuthenticationState();
+
+// Service used by the sample to describe where code is running
+builder.Services.AddScoped();
builder.Services.AddAuthentication(options =>
{
diff --git a/samples/Blazor/PerComponent/PerComponent/ServerRenderModeContext.cs b/samples/Blazor/PerComponent/PerComponent/ServerRenderModeContext.cs
index 11e10d7..27abf02 100644
--- a/samples/Blazor/PerComponent/PerComponent/ServerRenderModeContext.cs
+++ b/samples/Blazor/PerComponent/PerComponent/ServerRenderModeContext.cs
@@ -10,7 +10,8 @@ RenderMode IRenderModeContext.GetMode()
if(prerendering)
{
return RenderMode.Prerender;
- } else
+ }
+ else
{
return RenderMode.Server;
}
diff --git a/samples/Blazor/WebAssembly/WebAssembly.Client/Program.cs b/samples/Blazor/WebAssembly/WebAssembly.Client/Program.cs
index b30bbf4..1348424 100644
--- a/samples/Blazor/WebAssembly/WebAssembly.Client/Program.cs
+++ b/samples/Blazor/WebAssembly/WebAssembly.Client/Program.cs
@@ -3,7 +3,8 @@
var builder = WebAssemblyHostBuilder.CreateDefault(args);
-// authentication state and authorization
-builder.Services.AddBff();
+builder.Services
+ .AddBffBlazorClient() // Provides auth state provider that polls the /bff/user endpoint
+ .AddCascadingAuthenticationState();
await builder.Build().RunAsync();
diff --git a/samples/Blazor/WebAssembly/WebAssembly.Client/WebAssembly.Client.csproj b/samples/Blazor/WebAssembly/WebAssembly.Client/WebAssembly.Client.csproj
index 0984e58..b51a817 100644
--- a/samples/Blazor/WebAssembly/WebAssembly.Client/WebAssembly.Client.csproj
+++ b/samples/Blazor/WebAssembly/WebAssembly.Client/WebAssembly.Client.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+
diff --git a/samples/Blazor/WebAssembly/WebAssembly/Program.cs b/samples/Blazor/WebAssembly/WebAssembly/Program.cs
index d7362f6..49ed54e 100644
--- a/samples/Blazor/WebAssembly/WebAssembly/Program.cs
+++ b/samples/Blazor/WebAssembly/WebAssembly/Program.cs
@@ -5,7 +5,6 @@
var builder = WebApplication.CreateBuilder(args);
-// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
diff --git a/src/Duende.Bff.Blazor.Client/BffBlazorOptions.cs b/src/Duende.Bff.Blazor.Client/BffBlazorOptions.cs
index d9b83e4..5e73bf3 100644
--- a/src/Duende.Bff.Blazor.Client/BffBlazorOptions.cs
+++ b/src/Duende.Bff.Blazor.Client/BffBlazorOptions.cs
@@ -19,6 +19,16 @@ public class BffBlazorOptions
///
public string? RemoteApiBaseAddress { get; set; } = null;
+ ///
+ /// The delay, in milliseconds, before the AuthenticationStateProvider
+ /// will start polling the /bff/user endpoint. Defaults to 1000 ms.
+ ///
public int StateProviderPollingDelay { get; set; } = 1000;
+
+ ///
+ /// The delay, in milliseconds, between polling requests by the
+ /// AuthenticationStateProvider to the /bff/user endpoint. Defaults to
+ /// 5000 ms.
+ ///
public int StateProviderPollingInterval { get; set; } = 5000;
}
\ No newline at end of file
diff --git a/src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs b/src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs
index b39ac29..60fa9bc 100644
--- a/src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs
+++ b/src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs
@@ -21,6 +21,11 @@ public class BffClientAuthenticationStateProvider : AuthenticationStateProvider
private DateTimeOffset _userLastCheck = DateTimeOffset.MinValue;
private ClaimsPrincipal _cachedUser = new(new ClaimsIdentity());
+ ///
+ /// An intended for use in
+ /// Blazor WASM. It polls the /bff/user endpoint to monitor session
+ /// state.
+ ///
public BffClientAuthenticationStateProvider(
PersistentComponentState state,
IHttpClientFactory factory,
@@ -43,10 +48,8 @@ public override async Task GetAuthenticationStateAsync()
var user = await GetUser();
var state = new AuthenticationState(user);
- // checks periodically for a session state change and fires event
- // this causes a round trip to the server
- // adjust the period accordingly if that feature is needed
- if (user!.Identity!.IsAuthenticated)
+ // Periodically
+ if (user.Identity is { IsAuthenticated: true })
{
_logger.LogInformation("starting background check..");
Timer? timer = null;
@@ -54,7 +57,13 @@ public override async Task GetAuthenticationStateAsync()
timer = new Timer(async _ =>
{
var currentUser = await GetUser(false);
- // Always notify that auth state has changed, because the user management claims change over time
+ // 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)
diff --git a/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj b/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj
index 676c7ec..26e2eb1 100644
--- a/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj
+++ b/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Duende.Bff.Blazor.Client/ServiceCollectionExtensions.cs b/src/Duende.Bff.Blazor.Client/ServiceCollectionExtensions.cs
index 87ecef2..17ab858 100644
--- a/src/Duende.Bff.Blazor.Client/ServiceCollectionExtensions.cs
+++ b/src/Duende.Bff.Blazor.Client/ServiceCollectionExtensions.cs
@@ -10,7 +10,7 @@ namespace Duende.Bff.Blazor.Client;
public static class ServiceCollectionExtensions
{
- public static IServiceCollection AddBff(this IServiceCollection services,
+ public static IServiceCollection AddBffBlazorClient(this IServiceCollection services,
Action? configureAction = null)
{
if (configureAction != null)
@@ -21,7 +21,6 @@ public static IServiceCollection AddBff(this IServiceCollection services,
services
.AddAuthorizationCore()
.AddScoped()
- .AddCascadingAuthenticationState()
.AddTransient()
.AddHttpClient("BffAuthenticationStateProvider", (sp, client) =>
{
@@ -52,7 +51,7 @@ private static string GetRemoteApiPath(IServiceProvider sp)
return opt.Value.RemoteApiPath;
}
- private static Action SetBaseAddressInConfigureClient(
+ private static Action SetBaseAddress(
Action? configureClient)
{
return (sp, client) =>
@@ -62,7 +61,7 @@ private static Action SetBaseAddressInConfigureCli
};
}
- private static Action SetBaseAddressInConfigureClient(
+ private static Action SetBaseAddress(
Action? configureClient)
{
return (sp, client) =>
@@ -100,14 +99,14 @@ private static void SetBaseAddress(IServiceProvider sp, HttpClient client)
public static IHttpClientBuilder AddRemoteApiHttpClient(this IServiceCollection services, string clientName,
Action configureClient)
{
- return services.AddHttpClient(clientName, SetBaseAddressInConfigureClient(configureClient))
+ return services.AddHttpClient(clientName, SetBaseAddress(configureClient))
.AddHttpMessageHandler();
}
public static IHttpClientBuilder AddRemoteApiHttpClient(this IServiceCollection services, string clientName,
Action? configureClient = null)
{
- return services.AddHttpClient(clientName, SetBaseAddressInConfigureClient(configureClient))
+ return services.AddHttpClient(clientName, SetBaseAddress(configureClient))
.AddHttpMessageHandler();
}
@@ -115,7 +114,7 @@ public static IHttpClientBuilder AddRemoteApiHttpClient(this IServiceCollecti
Action configureClient)
where T : class
{
- return services.AddHttpClient(SetBaseAddressInConfigureClient(configureClient))
+ return services.AddHttpClient(SetBaseAddress(configureClient))
.AddHttpMessageHandler();
}
@@ -123,7 +122,7 @@ public static IHttpClientBuilder AddRemoteApiHttpClient(this IServiceCollecti
Action? configureClient = null)
where T : class
{
- return services.AddHttpClient(SetBaseAddressInConfigureClient(configureClient))
+ return services.AddHttpClient(SetBaseAddress(configureClient))
.AddHttpMessageHandler();
}
}
\ No newline at end of file
diff --git a/src/Duende.Bff.Blazor/CaptureManagementClaimsCookieEvents.cs b/src/Duende.Bff.Blazor/CaptureManagementClaimsCookieEvents.cs
index 9591650..1790f9c 100644
--- a/src/Duende.Bff.Blazor/CaptureManagementClaimsCookieEvents.cs
+++ b/src/Duende.Bff.Blazor/CaptureManagementClaimsCookieEvents.cs
@@ -6,6 +6,12 @@
namespace Duende.Bff.Blazor;
+///
+/// This subclass invokes the BFF to retrieve management claims and add them to the
+/// session. This is useful in interactive render modes where components are
+/// initialled rendered server side.
+///
public class CaptureManagementClaimsCookieEvents : CookieAuthenticationEvents
{
private readonly IClaimsService _claimsService;
diff --git a/src/Duende.Bff/Configuration/BffServiceCollectionExtensions.cs b/src/Duende.Bff/Configuration/BffServiceCollectionExtensions.cs
index 52c8570..ff49ed4 100644
--- a/src/Duende.Bff/Configuration/BffServiceCollectionExtensions.cs
+++ b/src/Duende.Bff/Configuration/BffServiceCollectionExtensions.cs
@@ -55,6 +55,7 @@ public static BffBuilder AddBff(this IServiceCollection services, Action, PostConfigureSlidingExpirationCheck>();
+ services.AddSingleton, PostConfigureApplicationCookieRevokeRefreshToken>();
services.AddSingleton, PostConfigureOidcOptionsForSilentLogin>();
diff --git a/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs b/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs
index 6a82bc0..c3e5abe 100644
--- a/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs
+++ b/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs
@@ -80,7 +80,7 @@ public virtual async Task ProcessRequestAsync(HttpContext context)
{
// In blazor, it is sometimes necessary to copy management claims into the session.
// So, we don't want duplicate mgmt claims. Instead, they should overwrite the existing mgmt claims
- // (in case they changed when the session slide, etc)
+ // (in case they changed when the session slid, etc)
var claims = (await GetUserClaimsAsync(result)).ToList();
var mgmtClaims = await GetManagementClaimsAsync(context, result);
diff --git a/src/Duende.Bff/SessionManagement/Configuration/PostConfigureApplicationCookieRefreshToken.cs b/src/Duende.Bff/SessionManagement/Configuration/PostConfigureApplicationCookieRefreshToken.cs
new file mode 100644
index 0000000..fbb2b1e
--- /dev/null
+++ b/src/Duende.Bff/SessionManagement/Configuration/PostConfigureApplicationCookieRefreshToken.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Duende Software. All rights reserved.
+// See LICENSE in the project root for license information.
+
+using IdentityModel;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading.Tasks;
+
+namespace Duende.Bff;
+
+///
+/// Cookie configuration to revoke refresh token on logout.
+///
+public class PostConfigureApplicationCookieRevokeRefreshToken : IPostConfigureOptions
+{
+ private readonly BffOptions _options;
+ private readonly string? _scheme;
+ private readonly ILogger _logger;
+
+ ///
+ /// ctor
+ ///
+ ///
+ ///
+ ///
+ public PostConfigureApplicationCookieRevokeRefreshToken(IOptions bffOptions, IOptions authOptions, ILogger logger)
+ {
+ _options = bffOptions.Value;
+ _scheme = authOptions.Value.DefaultAuthenticateScheme ?? authOptions.Value.DefaultScheme;
+ _logger = logger;
+ }
+
+ ///
+ public void PostConfigure(string? name, CookieAuthenticationOptions options)
+ {
+ if (_options.RevokeRefreshTokenOnLogout && name == _scheme)
+ {
+ options.Events.OnSigningOut = CreateCallback(options.Events.OnSigningOut);
+ }
+ }
+
+ private Func CreateCallback(Func inner)
+ {
+ async Task Callback(CookieSigningOutContext ctx)
+ {
+ _logger.LogDebug("Revoking user's refresh tokens in OnSigningOut for subject id: {subjectId}", ctx.HttpContext.User.FindFirst(JwtClaimTypes.Subject)?.Value);
+ await ctx.HttpContext.RevokeRefreshTokenAsync();
+ if (inner != null)
+ {
+ await inner.Invoke(ctx);
+ }
+ };
+
+ return Callback;
+ }
+}