Skip to content
Livestream: Are you sure your access tokens are really secure? Register Now!

BFF User Endpoint Extensibility

The BFF user endpoint can be customized by implementing the IUserEndpoint.

You can customize the behavior of the user endpoint by implementing the ProcessRequestAsync method of the IUserEndpoint interface. The default implementation can serve as a starting point for your own implementation.

If you want to extend the default behavior of the user endpoint, you can instead add a custom endpoint and call the original endpoint implementation:

Program.cs
var bffOptions = app.Services.GetRequiredService<IOptions<BffOptions>>().Value;
app.MapGet(bffOptions.UserPath, async (HttpContext context, CancellationToken ct) =>
{
// Custom logic before calling the original endpoint implementation
var endpointProcessor = context.RequestServices.GetRequiredService<IUserEndpoint>();
await endpointProcessor.ProcessRequestAsync(context, ct);
// Custom logic after calling the original endpoint implementation
});

There are several ways how you can enrich the claims for a specific user, depending on where the required data comes from.

To enrich claims for a user, you can implement a custom IClaimsTransformation. Claims transformation executes as part of the authentication process.

services.AddScoped<IClaimsTransformation, CustomClaimsTransformer>();
public class CustomClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
if (!identity.HasClaim(c => c.Type == "custom_claim"))
{
identity.AddClaim(new Claim("custom_claim", "your_value"));
}
return Task.FromResult(principal);
}
}

See the Claims Transformation topic in the ASP.NET Core documentation for more information.

User claims can be enriched by implementing the IUserEndpointClaimsEnricher interface. This interface is specific to the user endpoint and runs after authentication.

Because this runs within the user endpoint request, you can access the current HTTP context to retrieve the user’s access token. We recommend using the GetUserAccessTokenAsync extension method from Duende.AccessTokenManagement.OpenIdConnect, as it will automatically handle refreshing the token if it has expired.

Program.cs
builder.Services.AddTransient<IUserEndpointClaimsEnricher, CustomUserEndpointClaimsEnricher>();
CustomUserEndpointClaimsEnricher.cs
using Duende.Bff;
using Duende.Bff.Endpoints;
using Duende.AccessTokenManagement.OpenIdConnect;
using Microsoft.AspNetCore.Authentication;
public class CustomUserEndpointClaimsEnricher : IUserEndpointClaimsEnricher
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomUserEndpointClaimsEnricher(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<IReadOnlyList<ClaimRecord>> EnrichClaimsAsync(
AuthenticateResult authenticateResult,
IReadOnlyList<ClaimRecord> claims,
CancellationToken ct = default)
{
var newClaims = claims.ToList();
// Get the access token using the extension method
// This will automatically handle token refreshing if needed
var token = await _httpContextAccessor.HttpContext.GetUserAccessTokenAsync(cancellationToken: ct);
if (!string.IsNullOrEmpty(token.AccessToken))
{
// Call external API using the access token
// ...
}
// Add custom claims
newClaims.Add(new ClaimRecord("custom_data", "some value"));
return newClaims;
}
}