Skip to content

Commit

Permalink
Modernize code for API auth, Identity and BFF (#486)
Browse files Browse the repository at this point in the history
* Modernize code for API auth, Identity and BFF

* Additional modernization changes for BFF

---------

Co-authored-by: Roland Guijt <[email protected]>
  • Loading branch information
RolandGuijt and Roland Guijt authored Jun 20, 2024
1 parent 9dae555 commit 20dbc55
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 179 deletions.
8 changes: 4 additions & 4 deletions IdentityServer/v7/docs/content/apis/add_apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The value of *IdentityServerConstants.LocalApi.ScopeName* is *IdentityServerApi*
To enable token validation for local APIs, add the following to your IdentityServer startup:

```cs
services.AddLocalApiAuthentication();
builder.Services.AddLocalApiAuthentication();
```

To protect an API controller, decorate it with an *Authorize* attribute using the *LocalApi.PolicyName* policy:
Expand All @@ -63,7 +63,7 @@ Authorized clients can then request a token for the *IdentityServerApi* scope an
You can also add your endpoints to the discovery document if you want, e.g like this::

```cs
services.AddIdentityServer(options =>
builder.Services.AddIdentityServer(options =>
{
options.Discovery.CustomEntries.Add("local_api", "~/localapi");
})
Expand All @@ -84,7 +84,7 @@ This covers the most common scenarios. You can customize this behavior in the fo


```cs
services.AddAuthorization(options =>
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy =>
{
Expand All @@ -100,7 +100,7 @@ You can provide a callback to transform the claims of the incoming token after v
Either use the helper method, e.g.:

```cs
services.AddLocalApiAuthentication(principal =>
builder.Services.AddLocalApiAuthentication(principal =>
{
principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value"));

Expand Down
45 changes: 17 additions & 28 deletions IdentityServer/v7/docs/content/apis/aspnetcore/confirmation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,13 @@ If you are using a [mutual TLS connection]({{< ref "/tokens/pop/mtls" >}}) to es
You can do so with custom middleware like this:

```cs
public void Configure(IApplicationBuilder app)
{
// rest omitted
// normal token validation happens here
app.UseAuthentication();
// normal token validation happens here
app.UseAuthentication();

// This adds custom middleware to validate cnf claim
app.UseConfirmationValidation();

app.UseAuthorization();
// This adds custom middleware to validate cnf claim
app.UseConfirmationValidation();

// rest omitted
}
app.UseAuthorization();
```

Here, *UseConfirmationValidation* is an extension method that registers the middleware that performs the necessary validation:
Expand Down Expand Up @@ -118,23 +111,19 @@ Given that there are no off-the-shelf libraries that implement this, we have dev
With this sample the configuration necessary in your startup can be as simple as this:

```cs
// adds the normal JWT bearer validation
builder.Services.AddAuthentication("token")
.AddJwtBearer("token", options =>
{
options.Authority = Constants.Authority;
options.TokenValidationParameters.ValidateAudience = false;
options.MapInboundClaims = false;

public void ConfigureServices(IServiceCollection services)
{
// adds the normal JWT bearer validation
services.AddAuthentication("token")
.AddJwtBearer("token", options =>
{
options.Authority = Constants.Authority;
options.TokenValidationParameters.ValidateAudience = false;
options.MapInboundClaims = false;

options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});

// extends the "token" scheme above with DPoP processing and validation
services.ConfigureDPoPTokensForScheme("token");
}
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});

// extends the "token" scheme above with DPoP processing and validation
builder.Services.ConfigureDPoPTokensForScheme("token");
```

You can find this sample [here]({{< ref "/samples/misc#DPoP" >}}). To use the
Expand Down
48 changes: 16 additions & 32 deletions IdentityServer/v7/docs/content/apis/aspnetcore/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,21 @@ First you need to add a reference to the authentication handler in your API proj
If all you care about is making sure that an access token comes from your trusted IdentityServer, the following snippet shows the typical JWT validation configuration for ASP.NET Core:

```cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "https://demo.duendesoftware.com";
// base-address of your identityserver
options.Authority = "https://demo.duendesoftware.com";

// audience is optional, make sure you read the following paragraphs
// to understand your options
options.TokenValidationParameters.ValidateAudience = false;
// audience is optional, make sure you read the following paragraphs
// to understand your options
options.TokenValidationParameters.ValidateAudience = false;

// it's recommended to check the type header to avoid "JWT confusion" attacks
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
}
}
// it's recommended to check the type header to avoid "JWT confusion" attacks
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
```

{{% notice note %}}
On .NET Core 3.1 you need to manually reference the [System.IdentityModel.Tokens.Jwt](https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/5.6.0) Nuget package version 5.6 to be able to check the type header.
{{% /notice %}}

## Adding audience validation
Simply making sure that the token is coming from a trusted issuer is not good enough for most cases.
In more complex systems, you will have multiple resources and multiple clients. Not every client might be authorized to access every resource.
Expand All @@ -66,18 +56,12 @@ If you designed your APIs around the concept of [API resources]({{< ref "/fundam
If you want to express in your API, that only access tokens for the *api1* audience (aka API resource name) are accepted, change the above code snippet to:

```cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://demo.duendesoftware.com";
options.Audience = "api1";
options.Authority = "https://demo.duendesoftware.com";
options.Audience = "api1";

options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
}
}
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
```
4 changes: 2 additions & 2 deletions IdentityServer/v7/docs/content/apis/aspnetcore/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ weight: 20
If you are using [reference tokens]({{< ref "/tokens/reference" >}}), you need an authentication handler that implements the back-channel validation via the [OAuth 2.0 token introspection](https://tools.ietf.org/html/rfc7662) protocol, e.g. [this](https://github.com/IdentityModel/IdentityModel.AspNetCore.OAuth2Introspection) one:.

```cs
services.AddAuthentication("token")
builder.Services.AddAuthentication("token")
.AddOAuth2Introspection("token", options =>
{
options.Authority = Constants.Authority;
Expand All @@ -22,7 +22,7 @@ services.AddAuthentication("token")
It is not uncommon to use the same API with both JWTs and reference tokens. In this case you setup two authentication handlers, make one the default handler and provide some forwarding logic, e.g.:

```cs
services.AddAuthentication("token")
builder.Services.AddAuthentication("token")

// JWT tokens
.AddJwtBearer("token", options =>
Expand Down
17 changes: 8 additions & 9 deletions IdentityServer/v7/docs/content/aspnet_identity/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@ dotnet add package Duende.IdentityServer.AspNetIdentity

Next, configure ASP.NET Identity normally in your IdentityServer host with the standard calls to *AddIdentity* and any other related configuration.

Then in your *Startup.cs*, use the *AddAspNetIdentity* extension method after the call to *AddIdentityServer*:
Then in your *Program.cs*, use the *AddAspNetIdentity* extension method after the call to *AddIdentityServer*:

public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
```cs
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

services.AddIdentityServer()
.AddAspNetIdentity<ApplicationUser>();
}
builder.Services.AddIdentityServer()
.AddAspNetIdentity<ApplicationUser>();
```

*AddAspNetIdentity* requires as a generic parameter the class that models your user for ASP.NET Identity (and the same one passed to *AddIdentity* to configure ASP.NET Identity).
This configures IdentityServer to use the ASP.NET Identity implementations of [IUserClaimsPrincipalFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.iuserclaimsprincipalfactory-1) to convert the user data into claims, *IResourceOwnerPasswordValidator* to support the [password grant type]({{<ref "/tokens/password_grant">}}), and *IProfileService* which uses the *IUserClaimsPrincipalFactory* to add [claims]({{<ref "/fundamentals/claims">}}) to tokens.
Expand Down
73 changes: 31 additions & 42 deletions IdentityServer/v7/docs/content/bff/apis/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,30 @@ Duende.BFF can automate both the pre-processing step of requiring the custom ant
Add the BFF middleware to the pipeline by calling *UseBFF*. Note that the middleware must be placed before the authorization middleware, but after routing.

```csharp
public void Configure(IApplicationBuilder app)
{
// rest omitted
app.UseAuthentication();
app.UseRouting();

app.UseBff();

app.UseAuthorization();

app.UseEndpoints(endpoints => { /* ... */ }
}
//map endpoints
```

#### Decorate Endpoints
Endpoints that require the pre and post processing described above must be decorated with a call to *AsBffApiEndpoint()*.

For minimal API endpoints, you can apply BFF pre- and post-processing when they are mapped.
```csharp
endpoints.MapPost("/foo", context => { ... })
app.MapPost("/foo", context => { ... })
.RequireAuthorization() // no anonymous access
.AsBffApiEndpoint(); // BFF pre/post processing
```


For MVC controllers, you can similarly apply BFF pre- and post-processing to controller actions when they are mapped.
```csharp
endpoints.MapControllers()
app.MapControllers()
.RequireAuthorization() // no anonymous access
.AsBffApiEndpoint(); // BFF pre/post processing
```
Expand All @@ -133,22 +128,19 @@ However, if you are defending against CSRF attacks with some other mechanism, yo
For *version 1.x*, set the *requireAntiForgeryCheck* parameter to *false* when adding the endpoint. For example:

```csharp
app.UseEndpoints(endpoints =>
{
// MVC controllers
endpoints.MapControllers()
.RequireAuthorization()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.AsBffApiEndpoint(requireAntiforgeryCheck: false);

// simple endpoint
endpoints.MapPost("/foo", context => { /* ... */ })
.RequireAuthorization()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.AsBffApiEndpoint(requireAntiforgeryCheck: false);
});
// MVC controllers
app.MapControllers()
.RequireAuthorization()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.AsBffApiEndpoint(requireAntiforgeryCheck: false);

// simple endpoint
app.MapPost("/foo", context => { /* ... */ })
.RequireAuthorization()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.AsBffApiEndpoint(requireAntiforgeryCheck: false);
```

On MVC controllers and actions you can set the *RequireAntiForgeryCheck* as a flag in the *BffApiAttribute*, like this:
Expand All @@ -166,24 +158,21 @@ public class SampleApiController : ControllerBase
In *version 2.x*, use the *SkipAntiforgery* fluent API when adding the endpoint. For example:

```csharp
app.UseEndpoints(endpoints =>
{
// MVC controllers
endpoints.MapControllers()
.RequireAuthorization()
.AsBffApiEndpoint()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.SkipAntiforgery();

// simple endpoint
endpoints.MapPost("/foo", context => { /* ... */ })
.RequireAuthorization()
.AsBffApiEndpoint()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.SkipAntiforgery();
});
// MVC controllers
app.MapControllers()
.RequireAuthorization()
.AsBffApiEndpoint()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.SkipAntiforgery();

// simple endpoint
app.MapPost("/foo", context => { /* ... */ })
.RequireAuthorization()
.AsBffApiEndpoint()
// WARNING: Disabling antiforgery protection may make
// your APIs vulnerable to CSRF attacks
.SkipAntiforgery();
```

MVC controllers and actions can use the *BffApiSkipAntiforgeryAttribute* (which is independent of the *BffApiAttribute*), like this:
Expand Down
10 changes: 3 additions & 7 deletions IdentityServer/v7/docs/content/bff/apis/remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ To enable this feature, add a reference to the *Duende.BFF.Yarp* NuGet package,
#### Add Remote API Service to DI

```cs
services.AddBff()
builder.Services.AddBff()
.AddRemoteApis();
```

Expand All @@ -27,12 +27,8 @@ services.AddBff()
Use the *MapRemoteBffApiEndpoint* extension method to describe how to map requests coming into the BFF out to remote APIs and the *RequireAccessToken* method to specify token requirements. *MapRemoteBffApiEndpoint* takes two parameters: the base path of requests that will be mapped externally, and the address to the external API where the requests will be mapped. *MapRemoteBffApiEndpoint* maps a path and all sub-paths below it. The intent is to allow easy mapping of groups of URLs. For example, you can set up mappings for the /users, /users/{userId}, /users/{userId}/books, and /users/{userId}/books/{bookId} endpoints like this:

```cs
app.UseEndpoints(endpoints =>
{
endpoints.MapRemoteBffApiEndpoint(
"/api/users", "https://remoteHost/users")
.RequireAccessToken(TokenType.User);
});
app.MapRemoteBffApiEndpoint("/api/users", "https://remoteHost/users")
.RequireAccessToken(TokenType.User);
```

{{% notice note %}}
Expand Down
Loading

0 comments on commit 20dbc55

Please sign in to comment.