Samples icon indicating copy to clipboard operation
Samples copied to clipboard

Update Blazor Samples For .NET 8

Open josephdecock opened this issue 2 years ago • 12 comments

josephdecock avatar Aug 18 '23 15:08 josephdecock

I second this, I have been looking all over the web trying to find an example of how to properly implement SSR with Auto Mode (SignalR and WASM). I have tried various configurations with BFF and Server Side but have not had much luck. If there is a working example out there that would be greatly that we could repost or if one could be added to this repo, that would be great help in trying to solve my problem.

b1tzer0 avatar Oct 09 '23 16:10 b1tzer0

It needs a BlazorSSR example with Asp.Net Core Identity, so to have the default user management solution handled.

lnaie avatar Nov 27 '23 18:11 lnaie

What we need is a Blazor Web App sample that uses BFF for coordinating authentication and API calls that works for all render modes.

leastprivilege avatar Nov 28 '23 07:11 leastprivilege

If possible, an example (or explanation somewhere) of how to use it with SignalR from a Blazor WASM page/component would be very welcome.

hugh-maaskant avatar Nov 28 '23 10:11 hugh-maaskant

What we need is a Blazor Web App sample that uses BFF for coordinating authentication and API calls that works for all render modes.

One sample that covers all rendering modes is ideal (https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0). I was thinking that it would help, for now, to target the Interactive WebAssembly, which renders on the server, as well on the client.

lnaie avatar Nov 28 '23 15:11 lnaie

Is there any approximate date when we can expect the release of the updated samples?

Liandrel avatar May 21 '24 15:05 Liandrel

@Liandrel , I managed to get this working with an app I am working on. While I can't show source, I can point you to the same location where I found my starting point. One caveat is that while it does work; it is not integrated into Duende at this time. https://github.com/josephdecock/InteractiveBlazorAuth/tree/main

Hope this helps

b1tzer0 avatar May 21 '24 16:05 b1tzer0

@b1tzer0, did you get "all of it" working based on that InteractiveBlazorAuth example? Based on your post, I also tried it out and run into issues, some of which are mentioned further above. For example:

  • GET https://localhost:7103/bff/user leads to a 401 (as mentioned above)
  • GET https://localhost:7103/bff/logout leads to an exception "Invalid Session Id" in Duende.Bff.DefaultLogoutService.ProcessRequestAsync(HttpContext context)

@josephdecock, I would essentially like to set up a new ASP.NET 8.0 Blazor Web App with Interactive WebAssembly (avoiding SignalR) plus a Web API and then add IdentityServer-based authentication and authorization. While your InteractiveBlazorAuth example looks like a good starting point (which does more than what I need because it demonstrates render modes that I likely don't need), it has the issues mentioned above. So, unfortunately, there is currently no adequate, working example that demonstrates the mere basics.

ThomasBarnekow avatar Jun 25 '24 16:06 ThomasBarnekow

@ThomasBarnekow, I can confirm the /bff/user appears to work in both WASM and Blazor Server (SignalR) image

Here you can see the only websocket is the browser refresh: image

Logout is hit and miss, while in debug it does in fact take me to the logout page, but if I am running outside of debug mode the page just refreshes. Looking at it, I feel like I messed that up myself, when I go directly to the URL it works with no problem.

This is the code I am using for my bff authentication state provider.

public class BffAuthenticationStateProvider : AuthenticationStateProvider
{
    private static readonly TimeSpan UserCacheRefreshInterval
    = TimeSpan.FromSeconds(60);

    private readonly HttpClient _client;
    private readonly ILogger<BffAuthenticationStateProvider> _logger;

    private DateTimeOffset _userLastCheck
        = DateTimeOffset.FromUnixTimeSeconds(0);
    private ClaimsPrincipal _cachedUser
        = new ClaimsPrincipal(new ClaimsIdentity());

    public BffAuthenticationStateProvider(
        HttpClient client,
        ILogger<BffAuthenticationStateProvider> logger)
    {
        _client = client;
        _logger = logger;
    }

    public override async Task<AuthenticationState> 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)
        {
            _logger.LogInformation("starting background check..");
            Timer? timer = null;

            timer = new Timer(async _ =>
            {
                var currentUser = await GetUser(false);
                if (currentUser.Identity.IsAuthenticated == false)
                {
                    _logger.LogInformation("user logged out");
                    NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(currentUser)));
                    await timer.DisposeAsync();
                }
            }, null, 1000, 5000);
        }

        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;
    }

    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");

            if (response.StatusCode == HttpStatusCode.OK)
            {
                var claims = await response.Content.ReadFromJsonAsync<List<ClaimRecord>>();

                var identity = new ClaimsIdentity(
                    nameof(BffAuthenticationStateProvider),
                    "name",
                    "role");

                foreach (var claim in claims)
                {
                    identity.AddClaim(new Claim(claim.Type, claim.Value.ToString()));
                }

                return new ClaimsPrincipal(identity);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Fetching user failed.");
        }

        return new ClaimsPrincipal(new ClaimsIdentity());
    }
}

Here is where I wire it up in my .Client startup file

builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, BffAuthenticationStateProvider>();

// HTTP client configuration
builder.Services.AddTransient<AntiforgeryHandler>();

builder.Services.AddHttpClient("HostUri", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<AntiforgeryHandler>();

b1tzer0 avatar Jun 25 '24 17:06 b1tzer0

Is there an update to this issue? We've got an existing set of Blazor WASM applications that are using BFF, and we want to move across to the new rendering modes in .NET 8/9. I can't see any updated documentation unfortunately.

thesilverbadger avatar Sep 30 '24 09:09 thesilverbadger

Second this, looking for updated samples.

VictorioBerra avatar Oct 03 '24 23:10 VictorioBerra

I did notice some updated samples in the BFF repo in the tag for release 3 preview 2 https://github.com/DuendeSoftware/BFF/tree/3.0.0-preview.2/samples/Blazor

Not sure if it is what you are looking for though. I didn't notice anything that was glaring at me saying this is what you want for auto.

Sorry for the short response, on my mobile device

b1tzer0 avatar Oct 04 '24 00:10 b1tzer0