Skip to content
Merged
7 changes: 7 additions & 0 deletions .idea/dictionaries/project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ WebStorm has Grazie as a built-in spell checker and grammar checker, and support
* You can link to header anchors using the `#` symbol, for example `[multiple authentication methods](/identityserver/ui/federation.md#multiple-authentication-methods-for-users)`.
* Link relevant text. Prefer `learn more about [improving the sign-in experience]` over `click [here] to learn more`.
* Run `npm run linkchecker` to validate all links (note this will ignore links to GitHub because of rate limits in place).
* When a markdown link is long (75+ characters) or a link is repeated multiple times on a page, prefer moving the link to the bottom of the file and using markdown anchor syntax `[test.cs][repo-test-file]`

### Code Block Style

Expand All @@ -74,19 +75,23 @@ WebStorm has Grazie as a built-in spell checker and grammar checker, and support
* Make sure examples are runnable and complete. The goal is "Copy-paste from docs". Include namespaces, a result, and other prerequisites that are not obvious to someone new to the code.
* Inline comments can be used to explain essential parts of the code. Expressive code can highlight line numbers, show diffs, and more.
* Mention NuGet packages as a `bash` code block showing how to install it (`dotnet add package ...`). Link to the NuGet Gallery.
* When referencing a property, field, class, or other symbol in text, use the `test` format instead of *test*.
* Values should also be back-ticked, especially HTTP Status codes like `404` or `401`.
* Make sure code blocks start at the very first character space and don't have excessive starting padding.

### Frontmatter Rules

* Always have a `title` property to set the page title.
* Always have a `description` property to set the page description.
* Always have a `description` property to set the page description. This is a summary of the page's core content.
* Always have a `date` property to set the creation/significant update date for a page. Use the `YYYY-MM-DD` format.
* Add the `sidebar` property and include the `label`. The `label` is used in the menu, and should typically be shorter than the more descriptive `title`. For example:
* Add the `sidebar` property and must include the `label` and `order`. The `label` is used in the menu, and should typically be shorter than the more descriptive `title`. For example:

```yaml
title: "Using IdentityServer As A Federation Gateway"
sidebar:
label: "Federation"
order: 1
```
* In the `sidebar` property, use `order` to re-order entries in the navigation bar.

## 🧞 Commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Client Assertions
description: Learn how to use client assertions instead of shared secrets for token client authentication in Duende.AccessTokenManagement.
sidebar:
label: Client Assertions
order: 30
redirect_from:
- /foss/accesstokenmanagement/advanced/client_assertions/
Expand All @@ -14,34 +15,36 @@ If your token client is using a client assertion instead of a shared secret, you

Here's a sample client assertion service using the Microsoft JWT library:

```cs
public class ClientAssertionService : IClientAssertionService
{
private readonly IOptionsSnapshot<ClientCredentialsClient> _options;

public ClientAssertionService(IOptionsSnapshot<ClientCredentialsClient> options)
{
_options = options;
}
```csharp
// ClientAssertionService.cs
using Duende.AccessTokenManagement;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

public class ClientAssertionService(IOptionsSnapshot<ClientCredentialsClient> options)
: IClientAssertionService
{
public Task<ClientAssertion?> GetClientAssertionAsync(
string? clientName = null, TokenRequestParameters? parameters = null)
{
if (clientName == "invoice")
{
var options = _options.Get(clientName);
var options1 = options.Get(clientName);

var descriptor = new SecurityTokenDescriptor
{
Issuer = options.ClientId,
Audience = options.TokenEndpoint,
Issuer = options1.ClientId,
Audience = options1.TokenEndpoint,
Expires = DateTime.UtcNow.AddMinutes(1),
SigningCredentials = GetSigningCredential(),

Claims = new Dictionary<string, object>
{
{ JwtClaimTypes.JwtId, Guid.NewGuid().ToString() },
{ JwtClaimTypes.Subject, options.ClientId! },
{ JwtClaimTypes.Subject, options1.ClientId! },
{ JwtClaimTypes.IssuedAt, DateTime.UtcNow.ToEpochTime() }
},

Expand All @@ -63,6 +66,11 @@ public class ClientAssertionService : IClientAssertionService

return Task.FromResult<ClientAssertion?>(null);
}

private SigningCredentials GetSigningCredential()
{
throw new NotImplementedException();
}
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ redirect_from:
- /foss/accesstokenmanagement/advanced/client_credentials/
---

The most common way to use the access token management for machine to machine communication is described [here](/accesstokenmanagement/workers) - however you may want to customize certain aspects of it - here's what you can do.
The most common way to use the access token management for [machine-to-machine communication](/accesstokenmanagement/workers) - however, you may want to customize certain aspects of it. Here's what you can do.

## Client options
## Client Options

You can add token client definitions to your host while configuring the ASP.NET Core service provider, e.g.:

```cs
```csharp
// Program.cs
services.AddClientCredentialsTokenManagement()
.AddClient("invoices", client =>
{
Expand All @@ -40,7 +41,8 @@ You can set the following options:

Internally the standard .NET options system is used to register the configuration. This means you can also register clients like this:

```cs
```csharp
// Program.cs
services.Configure<ClientCredentialsClient>("invoices", client =>
{
client.TokenEndpoint = "https://sts.company.com/connect/token";
Expand All @@ -55,45 +57,51 @@ services.Configure<ClientCredentialsClient>("invoices", client =>

Or use the `IConfigureNamedOptions` if you need access to the ASP.NET Core service provider during registration, e.g.:

```cs
public class ClientCredentialsClientConfigureOptions : IConfigureNamedOptions<ClientCredentialsClient>
{
private readonly DiscoveryCache _cache;
```csharp
// ClientCredentialsClientConfigureOptions.cs
using Duende.AccessTokenManagement;
using Duende.IdentityModel.Client;
using Microsoft.Extensions.Options;

public ClientCredentialsClientConfigureOptions(DiscoveryCache cache)
{
_cache = cache;
}

public void Configure(string name, ClientCredentialsClient options)
public class ClientCredentialsClientConfigureOptions(DiscoveryCache cache)
: IConfigureNamedOptions<ClientCredentialsClient>
{
public void Configure(string? name, ClientCredentialsClient options)
{
if (name == "invoices")
{
var disco = _cache.GetAsync().GetAwaiter().GetResult();
var disco = cache.GetAsync().GetAwaiter().GetResult();

options.TokenEndpoint = disco.TokenEndpoint;

client.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57";
client.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c";
options.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57";
options.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c";

client.Scope = "list";
client.Resource = "urn:invoices";
options.Scope = "list";
options.Resource = "urn:invoices";
}
}

public void Configure(ClientCredentialsClient options)
{
// implement default configure
Configure("", options);
}
}
```

You will also need to register the config options, for example:

```cs
```csharp
// Program.cs
services.AddClientCredentialsTokenManagement();

services.AddSingleton(new DiscoveryCache("https://sts.company.com"));
services.AddSingleton<IConfigureOptions<ClientCredentialsClient>,
ClientCredentialsClientConfigureOptions>();
```

### Backchannel communication
### Backchannel Communication

By default, all backchannel communication will be done using a named client from the HTTP client factory. The name is `Duende.AccessTokenManagement.BackChannelHttpClient` which is also a constant called `ClientCredentialsTokenManagementDefaults.BackChannelHttpClientName`.

Expand All @@ -111,6 +119,7 @@ By default, tokens will be cached using the `IDistributedCache` abstraction in A
For development purposes, you can use the `MemoryDistributedCache`:

```cs
// Program.cs
services.AddDistributedMemoryCache();
```

Expand All @@ -122,6 +131,7 @@ For production deployments, we recommend using a [distributed cache](https://lea
The built-in cache in `Duende.AccessTokenManagment` uses two settings from the options, which apply with any `IDistributedCache`:

```cs
// Program.cs
services.AddClientCredentialsTokenManagement(options =>
{
options.CacheLifetimeBuffer = 60;
Expand Down
18 changes: 14 additions & 4 deletions src/content/docs/accesstokenmanagement/advanced/dpop.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
title: DPoP
description: DPoP (Demonstrating Proof-of-Possession) is a security mechanism that binds access tokens to specific cryptographic keys to prevent token theft and misuse.
title: Demonstrating Proof-of-Possession (DPoP)
description: Demonstrating Proof-of-Possession is a security mechanism that binds access tokens to specific cryptographic keys to prevent token theft and misuse.
sidebar:
label: DPoP
order: 40
redirect_from:
- /foss/accesstokenmanagement/advanced/dpop/
Expand All @@ -19,11 +20,18 @@ The creation and management of this DPoP key is up to the policy of the client.

Creating a JWK in .NET is simple:

```cs
```csharp
// Program.cs
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.IdentityModel.Tokens;

var rsaKey = new RsaSecurityKey(RSA.Create(2048));
var jwkKey = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey);
jwkKey.Alg = "PS256";
var jwk = JsonSerializer.Serialize(jwkKey);

Console.WriteLine(jwk);
```

## Key Configuration
Expand All @@ -36,6 +44,7 @@ Once you have a JWK you wish to use, then it must be configured or made availabl
Here's a sample configuring the key in an application using `AddOpenIdConnectAccessTokenManagement` in the startup code:

```cs
// Program.cs
services.AddOpenIdConnectAccessTokenManagement(options =>
{
options.DPoPJsonWebKey = jwk;
Expand All @@ -45,6 +54,7 @@ services.AddOpenIdConnectAccessTokenManagement(options =>
Similarly, for an application using `AddClientCredentialsTokenManagement`, it would look like this:

```cs
// Program.cs
services.AddClientCredentialsTokenManagement()
.AddClient("client_name", options =>
{
Expand All @@ -61,7 +71,7 @@ There is nothing explicit needed on behalf of the developer using this library.

When using DPoP and `AddOpenIdConnectAccessTokenManagement`, this library will also automatically include the `dpop_jkt` parameter to the authorize endpoint.

## Proof Tokens at the API
## Proof Tokens At The API

Once the library has obtained a DPoP bound access token for the client, then if your application is using any of the `HttpClient` client factory helpers (e.g. `AddClientCredentialsHttpClient` or `AddUserAccessTokenHttpClient`) then those outbound HTTP requests will automatically include a DPoP proof token for the associated DPoP access token.

Expand Down
35 changes: 23 additions & 12 deletions src/content/docs/accesstokenmanagement/advanced/user-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@ redirect_from:
- /foss/accesstokenmanagement/advanced/user_tokens/
---

The most common way to use the access token management for interactive web applications is described [here](/accesstokenmanagement/web-apps/) - however you may want to customise certain aspects of it - here's what you can do.
The most common way
to use [access token management is for interactive web applications](/accesstokenmanagement/web-apps.md) -
however, you may want to customize certain aspects of it. Here's what you can do.

## General options
## General Options

You can pass in some global options when registering token management in the ASP.NET Core service provider.

* `ChallengeScheme` - by default the OIDC configuration is inferred from the default challenge scheme. This is recommended approach. If for some reason your OIDC handler is not the default challenge scheme, you can set the scheme name on the options
* `UseChallengeSchemeScopedTokens` - the general assumption is that you only have one OIDC handler configured. If that is not the case, token management needs to maintain multiple sets of token artefacts simultaneously. You can opt in to that feature using this setting.
* `ClientCredentialsScope` - when requesting client credentials tokens from the OIDC provider, the scope parameter will not be set since its value cannot be inferred from the OIDC configuration. With this setting you can set the value of the scope parameter.
* `ChallengeScheme` - by default the OIDC configuration is inferred from the default challenge scheme. This is
recommended approach. If for some reason your OIDC handler is not the default challenge scheme, you can set the scheme
name on the options
* `UseChallengeSchemeScopedTokens` - the general assumption is that you only have one OIDC handler configured. If that
is not the case, token management needs to maintain multiple sets of token artefacts simultaneously. You can opt in to
that feature using this setting.
* `ClientCredentialsScope` - when requesting client credentials tokens from the OIDC provider, the scope parameter will
not be set since its value cannot be inferred from the OIDC configuration. With this setting you can set the value of
the scope parameter.
* `ClientCredentialsResource` - same as previous, but for the resource parameter
* `ClientCredentialStyle` - specifies how client credentials are transmitted to the OIDC provider

```cs
```csharp
// Program.cs
builder.Services.AddOpenIdConnectAccessTokenManagement(options =>
{
options.ChallengeScheme = "schmeName";
Expand All @@ -34,7 +43,7 @@ builder.Services.AddOpenIdConnectAccessTokenManagement(options =>

## Per Request Parameters

You can also modify token management parameters on a per-request basis.
You can also modify token management parameters on a per-request basis.

The `UserTokenRequestParameters` class can be used for that:

Expand All @@ -47,20 +56,21 @@ The `UserTokenRequestParameters` class can be used for that:

The request parameters can be passed via the manual API:

```cs
```csharp
var token = await _tokenManagementService.GetAccessTokenAsync(User, new UserAccessTokenRequestParameters { ... });
```

...the extension methods

```cs
```csharp
var token = await HttpContext.GetUserAccessTokenAsync(
new UserTokenRequestParameters { ... });
```

...or the HTTP client factory

```cs
```csharp
// Program.cs
// registers HTTP client that uses the managed user access token
builder.Services.AddUserAccessTokenHttpClient("invoices",
parameters: new UserTokenRequestParameters { ... },
Expand All @@ -77,9 +87,10 @@ builder.Services.AddHttpClient<InvoiceClient>(client =>
.AddUserAccessTokenHandler(new UserTokenRequestParameters { ... });
```

## Token storage
## Token Storage

By default, the user's access and refresh token will be store in the ASP.NET Core authentication session (implemented by the cookie handler).
By default, the user's access and refresh token will be store in the ASP.NET Core authentication session (implemented by
the cookie handler).

You can modify this in two ways

Expand Down
Loading