Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
Added Duende.Bff.Blazor
Browse files Browse the repository at this point in the history
  • Loading branch information
josephdecock committed Aug 28, 2024
1 parent a3cc773 commit d37bd36
Show file tree
Hide file tree
Showing 28 changed files with 931 additions and 175 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# MacOs
.DS_Store

# Rider
.idea

Expand Down
20 changes: 16 additions & 4 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<FrameworkVersionRuntime>8.0.0</FrameworkVersionRuntime>
<FrameworkVersionTesting>8.0.0</FrameworkVersionTesting>
<FrameworkVersionTesting>8.0.8</FrameworkVersionTesting>
<WilsonVersion>7.1.2</WilsonVersion> <!-- Used in samples -->
<YarpVersion>2.1.0</YarpVersion>
<IdentityServerVersion>7.0.4</IdentityServerVersion>
<IdentityServerVersion>7.0.6</IdentityServerVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -13,15 +14,26 @@

<!-- runtime -->
<PackageReference Update="IdentityModel" Version="7.0.0" />
<PackageReference Update="Duende.AccessTokenManagement.OpenIdConnect" Version="3.0.0-preview.3" />
<PackageReference Update="Duende.AccessTokenManagement.OpenIdConnect" Version="3.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Relational" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Microsoft.Extensions.Http" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Microsoft.AspNetCore.Components.WebAssembly" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Microsoft.AspNetCore.Components.Authorization" Version="$(FrameworkVersionRuntime)" />
<PackageReference Update="Yarp.ReverseProxy" Version="$(YarpVersion)" />

<!-- samples -->
<PackageReference Update="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Update="Microsoft.IdentityModel.JsonWebTokens" Version="$(WilsonVersion)" />
<PackageReference Update="System.IdentityModel.Tokens.Jwt" Version="$(WilsonVersion)" />

<!-- testing -->
<PackageReference Update="Microsoft.EntityFrameworkCore.InMemory" Version="$(FrameworkVersionTesting)" />
<PackageReference Update="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(FrameworkVersionTesting)" />
<PackageReference Update="Microsoft.AspNetCore.TestHost" Version="$(FrameworkVersionTesting)" />
<PackageReference Update="Microsoft.Extensions.TimeProvider.Testing" Version="$(FrameworkVersionTesting)" />
<!-- Test timeprovider is released separately from the framework, so we can't use FrameworkVersionTesting -->
<PackageReference Update="Microsoft.Extensions.TimeProvider.Testing" Version="8.8.0" />

<PackageReference Update="Duende.IdentityServer" Version="$(IdentityServerVersion)" />

Expand Down
60 changes: 31 additions & 29 deletions Duende.Bff.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34414.90
Expand Down Expand Up @@ -39,7 +39,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JS8.DPoP", "samples\JS8.DPo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JS8.EF", "samples\JS8.EF\JS8.EF.csproj", "{CBB98134-92F5-487D-8CA3-84C19FF46775}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor.Wasm", "Blazor.Wasm", "{7E6EA8BA-EE8B-450E-AE89-C4604C0DD326}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duende.Bff.Blazor", "src\Duende.Bff.Blazor\Duende.Bff.Blazor.csproj", "{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duende.Bff.Blazor.Client", "src\Duende.Bff.Blazor.Client\Duende.Bff.Blazor.Client.csproj", "{DDB9C401-6B1F-4727-A4CB-932034FBF94E}"
EndProject
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Wasm.Bff", "samples\Blazor.Wasm\Blazor.Wasm.Bff\Blazor.Wasm.Bff.csproj", "{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}"
EndProject
Expand Down Expand Up @@ -223,30 +226,30 @@ Global
{CBB98134-92F5-487D-8CA3-84C19FF46775}.Release|x64.Build.0 = Release|Any CPU
{CBB98134-92F5-487D-8CA3-84C19FF46775}.Release|x86.ActiveCfg = Release|Any CPU
{CBB98134-92F5-487D-8CA3-84C19FF46775}.Release|x86.Build.0 = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|x64.ActiveCfg = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|x64.Build.0 = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|x86.ActiveCfg = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Debug|x86.Build.0 = Debug|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|Any CPU.Build.0 = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|x64.ActiveCfg = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|x64.Build.0 = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|x86.ActiveCfg = Release|Any CPU
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3}.Release|x86.Build.0 = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|x64.Build.0 = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Debug|x86.Build.0 = Debug|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|Any CPU.Build.0 = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|x64.ActiveCfg = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|x64.Build.0 = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|x86.ActiveCfg = Release|Any CPU
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4}.Release|x86.Build.0 = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|x64.ActiveCfg = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|x64.Build.0 = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|x86.ActiveCfg = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Debug|x86.Build.0 = Debug|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|Any CPU.Build.0 = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|x64.ActiveCfg = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|x64.Build.0 = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|x86.ActiveCfg = Release|Any CPU
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84}.Release|x86.Build.0 = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|x64.ActiveCfg = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|x64.Build.0 = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|x86.ActiveCfg = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Debug|x86.Build.0 = Debug|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|Any CPU.Build.0 = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|x64.ActiveCfg = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|x64.Build.0 = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|x86.ActiveCfg = Release|Any CPU
{DDB9C401-6B1F-4727-A4CB-932034FBF94E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -266,9 +269,8 @@ Global
{B37CA136-3F20-4D8A-9677-E3A9C9D893EF} = {E14F66D1-EA3E-40C6-835A-91A4382D4646}
{D8757F0F-254E-495F-961F-0192F8C97E3F} = {E14F66D1-EA3E-40C6-835A-91A4382D4646}
{CBB98134-92F5-487D-8CA3-84C19FF46775} = {E14F66D1-EA3E-40C6-835A-91A4382D4646}
{7E6EA8BA-EE8B-450E-AE89-C4604C0DD326} = {E14F66D1-EA3E-40C6-835A-91A4382D4646}
{BC21ADB7-F2CA-44F0-B6ED-0405E1EFFFA3} = {7E6EA8BA-EE8B-450E-AE89-C4604C0DD326}
{4E69FCF6-AE76-4F6D-98B8-969E9D244AE4} = {7E6EA8BA-EE8B-450E-AE89-C4604C0DD326}
{E02DF032-C17F-4D0C-9CAA-3BD3CC9E4F84} = {3C549079-A502-4B40-B051-5278915AE91B}
{DDB9C401-6B1F-4727-A4CB-932034FBF94E} = {3C549079-A502-4B40-B051-5278915AE91B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3DAD5980-4688-4794-9CF0-6F3CB67194E7}
Expand Down
2 changes: 2 additions & 0 deletions build/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ internal static async Task Main(string[] args)
Run("dotnet", $"pack ./src/Duende.Bff/Duende.Bff.csproj -c Release -o {Directory.CreateDirectory(packOutput).FullName} --no-build --nologo");
Run("dotnet", $"pack ./src/Duende.Bff.EntityFramework/Duende.Bff.EntityFramework.csproj -c Release -o {Directory.CreateDirectory(packOutput).FullName} --no-build --nologo");
Run("dotnet", $"pack ./src/Duende.Bff.Yarp/Duende.Bff.Yarp.csproj -c Release -o {Directory.CreateDirectory(packOutput).FullName} --no-build --nologo");
Run("dotnet", $"pack ./src/Duende.Bff.Blazor/Duende.Bff.Blazor.csproj -c Release -o {Directory.CreateDirectory(packOutput).FullName} --no-build --nologo");
Run("dotnet", $"pack ./src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj -c Release -o {Directory.CreateDirectory(packOutput).FullName} --no-build --nologo");
});

Target(Targets.SignPackage, DependsOn(Targets.Pack, Targets.RestoreTools), () =>
Expand Down
14 changes: 14 additions & 0 deletions src/Duende.Bff.Blazor.Client/AntiforgeryHandler.cs
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);
}
}
34 changes: 34 additions & 0 deletions src/Duende.Bff.Blazor.Client/BffBlazorOptions.cs
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 src/Duende.Bff.Blazor.Client/BffClientAuthenticationStateProvider.cs
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 src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj
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>
Loading

0 comments on commit d37bd36

Please sign in to comment.