```
When you now run the Blazor application, you will see the following error in your browser console:
```plaintext
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot provide a value for property 'AuthenticationStateProvider' on type 'Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState'. There is no registered service of type 'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'.
```
`CascadingAuthenticationState` is an abstraction over an arbitrary authentication system. It internally relies on a service called `AuthenticationStateProvider` to return the required information about the current authentication state and the information about the currently logged on user.
This component needs to be implemented, and that’s what we’ll do next.
## Modifying The Frontend (Part 2)
[Section titled “Modifying The Frontend (Part 2)”](#modifying-the-frontend-part-2)
The BFF library has a server-side component that allows querying the current authentication session and state ( see [here](/bff/fundamentals/session/management/user/)). We will now add a Blazor `AuthenticationStateProvider` that will internally use this endpoint.
Add a file with the following content:
```cs
using System.Net;
using System.Net.Http.Json;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
namespace Blazor6.Client.BFF;
public class BffAuthenticationStateProvider
: AuthenticationStateProvider
{
private static readonly TimeSpan UserCacheRefreshInterval
= TimeSpan.FromSeconds(60);
private readonly HttpClient _client;
private readonly ILogger
_logger;
private DateTimeOffset _userLastCheck
= DateTimeOffset.FromUnixTimeSeconds(0);
private ClaimsPrincipal _cachedUser
= new ClaimsPrincipal(new ClaimsIdentity());
public BffAuthenticationStateProvider(
HttpClient client,
ILogger logger)
{
_client = client;
_logger = logger;
}
public override async Task GetAuthenticationStateAsync()
{
return new AuthenticationState(await GetUser());
}
private async ValueTask 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 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>();
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());
}
}
```
…and register it in the client’s `Program.cs`:
Program.cs
```cs
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped();
```
If you run the server app now again, you will see a different error:
Terminal
```bash
fail: Duende.Bff.Endpoints.BffMiddleware[1]
Anti-forgery validation failed. local path: '/bff/user'
```
This is due to the antiforgery protection that is applied automatically to the management endpoints in the BFF host. To properly secure the call, you need to add a static `X-CSRF` header to the call. See [here](/bff/fundamentals/apis/local/) for more background information.
This can be easily accomplished by a delegating handler that can be plugged into the default HTTP client used by the Blazor frontend. Let’s first add the handler:
```cs
public class AntiforgeryHandler : DelegatingHandler
{
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-CSRF", "1");
return base.SendAsync(request, cancellationToken);
}
}
```
…and register it in the client’s `Program.cs` (overriding the standard HTTP client configuration; requires package Microsoft.Extensions.Http):
Program.cs
```cs
// HTTP client configuration
builder.Services.AddTransient();
builder.Services.AddHttpClient("backend", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler();
builder.Services.AddTransient(sp => sp.GetRequiredService().CreateClient("backend"));
```
This requires an additional reference in the client project:
```plaintext
```
If you restart the application again, the logon/logoff logic should work now. In addition you can display the contents of the session on the main page by adding this code to `Index.razor`:
```plaintext
@page "/"
Home
Hello, Blazor BFF!
@foreach (var claim in @context.User.Claims)
{
- @claim.Type
- @claim.Value
}
```
## Securing The Local API
[Section titled “Securing The Local API”](#securing-the-local-api)
The standard Blazor template contains an API endpoint (`WeatherForecastController.cs`). Try invoking the weather page from the UI. It works both in logged in and anonymous state. We want to change the code to make sure, that only authenticated users can call the API.
The standard way in ASP.NET Core would be to add an authorization requirement to the endpoint, either on the controller/action or via the endpoint routing, e.g.:
```cs
app.MapControllers()
.RequireAuthorization();
```
When you now try to invoke the API anonymously, you will see the following error in the browser console:
```plaintext
Access to fetch at 'https://demo.duendesoftware.com/connect/authorize?client_id=...[shortened]... (redirected from 'https://localhost:5002/WeatherForecast') from origin 'https://localhost:5002' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```
This happens because the ASP.NET Core authentication plumbing is triggering a redirect to the OpenID Connect provider for authentication. What we really want in that case is an API friendly status code - 401 in this scenario.
This is one of the features of the BFF middleware, but you need to mark the endpoint as a BFF API endpoint for that to take effect:
```cs
app.MapControllers()
.RequireAuthorization()
.AsBffApiEndpoint();
```
After making this change, you should see a much better error message:
```plaintext
Response status code does not indicate success: 401 (Unauthorized).
```
The client code can properly respond to this, e.g. triggering a login redirect.
When you logon now and call the API, you can put a breakpoint server-side and inspect that the API controller has access to the claims of the authenticated user via the `.User` property.
## Setting Up A Blazor BFF client In IdentityServer
[Section titled “Setting Up A Blazor BFF client In IdentityServer”](#setting-up-a-blazor-bff-client-in-identityserver)
In essence a BFF client is “just” a normal authorization code flow client:
* use the code grant type
* set a client secret
* enable `AllowOfflineAccess` if you want to use refresh tokens
* enable the required identity and resource scopes
* set the redirect URIs for the OIDC handler
Below is a typical code snippet for the client definition:
```cs
var bffClient = new Client
{
ClientId = "bff",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://bff_host/signin-oidc" },
FrontChannelLogoutUri = "https://bff_host/signout-oidc",
PostLogoutRedirectUris = { "https://bff_host/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = { "openid", "profile", "remote_api" }
};
```
## Further Experiments
[Section titled “Further Experiments”](#further-experiments)
Our Blazor BFF [sample](/bff/samples/#blazor-wasm) is based on this Quickstart. In addition it shows concepts like
* better organization with components
* reacting to logout
* using the authorize attribute to trigger automatic redirects to the login page
-----
# Building Browser-Based Client Applications
> Overview of browser-based client application patterns and security considerations when implementing JavaScript clients with IdentityServer
When building browser-based or SPA applications using javascript, there are two main styles: those with a backend and those without.
Browser-based applications **with a backend** are more secure, making it the recommended style. This style uses the [“Backend For Frontend” pattern](https://duendesoftware.com/blog/20210326-bff), or “BFF” for short, which relies on the backend host to implement all the security protocol interactions with the token server. The `Duende.BFF` library is used in [this quickstart](/identityserver/quickstarts/javascript-clients/js-with-backend/) to easily support the BFF pattern.
Browser-based applications **without a backend** need to do all the security protocol interactions on the client-side, including driving user authentication and token requests, session and token management, and token storage. This leads to more complex JavaScript, cross-browser incompatibilities, and a considerably higher attack surface. Since this style inherently needs to store security sensitive artifacts (like tokens) in JavaScript reachable locations, this style is not recommended. **Consequently, we don’t offer a quickstart for this style**.
As the [“OAuth 2.0 for Browser-Based Apps” IETF/OAuth working group BCP document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps) says:
> there is no browser API that allows to store tokens in a completely secure way.
Additionally, modern browsers have recently added or are planning to add privacy features that can break some front-channel protocol interactions. See [here](/bff/#3rd-party-cookies) for more details.
-----
# Browser-Based Applications With A BFF
> Guide to building secure browser-based JavaScript applications using the Backend For Frontend (BFF) pattern with Duende.BFF library
Note
We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 3](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/3_AspNetCoreAndApis). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation).
In this quickstart, you will build a browser-based JavaScript client application with a backend. This means your application will have server-side code that supports the frontend application code. This is known as the Backend For Frontend (BFF) pattern.
You will implement the BFF pattern with the help of the `Duende.BFF` library. The backend will implement all the security protocol interactions with the token server and will be responsible for management of the tokens. The client-side JavaScript authenticates with the BFF using traditional cookie authentication. This simplifies the JavaScript in the client-side, and reduces the attack surface of the application.
The features that will be shown in this quickstart will allow the user to log in with IdentityServer, invoke a local API hosted in the backend (secured with cookie authentication), invoke a remote API running in a different host (secured with an access token), and logout of IdentityServer.
## New Project For The JavaScript Client And BFF
[Section titled “New Project For The JavaScript Client And BFF”](#new-project-for-the-javascript-client-and-bff)
Begin by creating a new project to host the JavaScript application and its BFF. A single project containing the front-end and its BFF facilitates cookie authentication - the front end and BFF need to be on the same host so that cookies will be sent from the front end to the BFF.
Create a new ASP.NET Core web application and add it to the solution by running the following commands from the `src` directory:
Terminal
```bash
dotnet new web -n JavaScriptClient
cd ..
dotnet sln add ./src/JavaScriptClient
```
### Add Additional NuGet Packages
[Section titled “Add Additional NuGet Packages”](#add-additional-nuget-packages)
Install NuGet packages to add BFF and OIDC support to the new project by running the following commands from the `src/JavaScriptClient` directory:
Terminal
```bash
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
dotnet add package Duende.BFF
dotnet add package Duende.BFF.Yarp
```
### Modify Hosting
[Section titled “Modify Hosting”](#modify-hosting)
Modify the `JavaScriptClient` project to run on `https://localhost:5003`. Its `Properties/launchSettings.json` should look like this:
```json
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"JavaScriptClient": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
```
### Add Services
[Section titled “Add Services”](#add-services)
In the BFF pattern, the server-side code triggers and receives OpenID Connect requests and responses. To do that, it needs the same services configured as the WebClient did in the prior [web application quickstart](/identityserver/quickstarts/3-api-access/). Additionally, the BFF services need to be added with `AddBff()`. In addition, the offline\_access scope is requested that will result in a refresh token that will be used by the BFF library to automatically refresh the access token for the remote API if needed.
Add the following to `src/JavaScriptClient/Program.cs`:
* Duende BFF v4
Program.cs
```csharp
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Duende.Bff.Yarp;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services
.AddBff()
.ConfigureOpenIdConnect(options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "bff";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
}
.ConfigureCookies()
.AddRemoteApis();
var app = builder.Build();
```
* Duende BFF v3
Program.cs
```csharp
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Duende.Bff.Yarp;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services
.AddBff()
.AddRemoteApis();
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "bff";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
});
var app = builder.Build();
```
### Add Middleware
[Section titled “Add Middleware”](#add-middleware)
Similarly, the middleware pipeline for this application will resemble the WebClient, with the addition of the BFF middleware and the BFF endpoints. Continue by adding the following to `src/JavaScriptClient/Program.cs`:
Program.cs
```cs
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseBff();
app.UseAuthorization();
app.MapBffManagementEndpoints();
app.Run();
```
### Add HTML And JavaScript Files
[Section titled “Add HTML And JavaScript Files”](#add-html-and-javascript-files)
Next, add HTML and JavaScript files for your client-side application to the `wwwroot` directory in the `JavaScriptClient` project. Create that directory (`src/JavaScriptClient/wwwroot`) and add an `index.html` and an `app.js` file to it.
*`index.html`*
The index.html file will be the main page in your application. It contains
* buttons for the user to login, logout, and call the APIs
* a `` container used to show messages to the user
* a `