Skip to content

Commit

Permalink
Merge pull request #504 from DuendeSoftware/roland/addtokenmanagementqs
Browse files Browse the repository at this point in the history
Add token management quickstart
  • Loading branch information
leastprivilege authored Jul 23, 2024
2 parents d0523e3 + dc5b5a1 commit e0e7d81
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 20 deletions.
18 changes: 0 additions & 18 deletions IdentityServer/v7/docs/content/quickstarts/3_api_access.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,21 +163,3 @@ Also add a link to the new page in _src/WebClient/Shared/\_Layout.cshtml_ with t

Make sure the _IdentityServer_ and _Api_ projects are running, start the
_WebClient_ and request _/CallApi_ after authentication.

## Further Reading - Access token lifetime management

By far the most complex task for a typical client is to manage the access token.
You typically want to

- request an access and refresh token at login time
- cache those tokens
- use the access token to call APIs until it expires
- use the refresh token to get a new access token
- repeat the process of caching and refreshing with the new token

ASP.NET Core has built-in facilities that can help you with some of those tasks
(like caching or sessions), but there is still quite some work left to do.
Consider using the
[Duende.AccessTokenManagement](https://github.com/DuendeSoftware/Duende.AccessTokenManagement/wiki)
library for help with access token lifetime management. It provides abstractions
for storing tokens, automatic refresh of expired tokens, etc.
136 changes: 136 additions & 0 deletions IdentityServer/v7/docs/content/quickstarts/3a_token_management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: "Token Management"
date: 2024-07-23T08:22:12+02:00
weight: 5
---

Welcome to this Quickstart for Duende IdentityServer!

The previous quickstart introduced [API access]({{< ref "3_api_access" >}}) with interactive applications, but by far the most complex task for a typical client is to manage the access token.

Given that the access token has a finite lifetime, you typically want to

- request a refresh token in addition to the access token at login time
- cache those tokens
- use the access token to call APIs until it expires
- use the refresh token to get a new access token
- repeat the process of caching and refreshing with the new token

ASP.NET Core has built-in facilities that can help you with some of those tasks
(like caching or sessions), but there is still quite some work left to do.
[Duende.AccessTokenManagement](https://github.com/DuendeSoftware/Duende.AccessTokenManagement/wiki)
can help. It provides abstractions for storing tokens, automatic refresh of expired tokens, etc.

## Requesting a refresh token

To allow the _web_ client to request a refresh token set the _AllowOfflineAccess_ property to true in the client configuration.

Update the _Client_ in _src/IdentityServer/Config.cs_ as follows:

```cs
new Client
{
ClientId = "web",
ClientSecrets = { new Secret("secret".Sha256()) },

AllowedGrantTypes = GrantTypes.Code,

// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },

// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowOfflineAccess = true,

AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"verification",
"api1"
}
}
```

To get the refresh token the _offline_access_ scope has to be requested by the client.

In _src/WebClient/Program.cs_ add the scope to the scope list:

```cs
options.Scope.Add("offline_access");
```

When running the solution the refresh token should now be visible under _Properties_ on the landing page of the client.

## Automatically refreshing an access token

In the WebClient project add a reference to the NuGet package `Duende.AccessTokenManagement.OpenIdConnect` and in _Program.cs_ add the needed types to dependency injection:

```cs
builder.Services.AddOpenIdConnectAccessTokenManagement();
```

In _CallApi.cshtml.cs_ update the method body of `OnGet` as follows:

```cs
public async Task OnGet()
{
var tokenInfo = await HttpContext.GetUserAccessTokenAsync();
var client = new HttpClient();
client.SetBearerToken(tokenInfo.AccessToken!);

var content = await client.GetStringAsync("https://localhost:6001/identity");

var parsed = JsonDocument.Parse(content);
var formatted = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true });

Json = formatted;
}
```

There are two changes here that utilize the AccessTokenManagement NuGet package:

- An object called tokenInfo containing all stored tokens is returned by the _GetUserAccessTokenAsync_ extension method. This will make sure the access token is _automatically refreshed_ using the refresh token if needed.
- The _SetBearerToken_ extension method on HttpClient is used for convenience to place the access token in the needed HTTP header.

## Using a Named HttpClient

On each call to OnGet in _CallApi.cshtml.cs_ a new HttpClient is created in the code above. Recommended however is to use the [HttpClientFactory](https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory) pattern so that instances can be reused.

`Duende.AccessTokenManagement.OpenIdConnect` builds on top of _HttpClientFactory_ to create HttpClient instances that automatically retrieve the needed access token and refresh if needed.

In the client in _Program.cs_ under the call to _AddOpenIdConnectAccessTokenManagement_ register the HttpClient:

```cs
builder.Services.AddUserAccessTokenHttpClient("apiClient", configureClient: client =>
{
client.BaseAddress = new Uri("https://localhost:6001");
});
```

Now the _OnGet_ method in _CallApi.cshtml.cs_ can be even more straightforward:

```cs
public class CallApiModel(IHttpClientFactory httpClientFactory) : PageModel
{
public string Json = string.Empty;

public async Task OnGet()
{
var client = httpClientFactory.CreateClient("apiClient");

var content = await client.GetStringAsync("https://localhost:6001/identity");

var parsed = JsonDocument.Parse(content);
var formatted = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true });

Json = formatted;
}
}
```

Note that:

- The httpClientFactory is injected using a primary constructor. The type was registered when _AddOpenIdConnectAccessTokenManagement_ was called in _Program.cs_.
- The client is created using the factory passing in the name of the client that was registered in _program.cs_.
- No additional code is needed. The client will automatically retrieve the access token and refresh it if needed.
2 changes: 1 addition & 1 deletion IdentityServer/v7/docs/content/quickstarts/4_ef.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Using EntityFramework Core for configuration and operational data"
date: 2020-09-10T08:22:12+02:00
weight: 5
weight: 6
---

Welcome to Quickstart 4 for Duende IdentityServer! In this quickstart you will
Expand Down
2 changes: 1 addition & 1 deletion IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Using ASP.NET Core Identity"
date: 2020-09-10T08:22:12+02:00
weight: 6
weight: 7
---

Welcome to Quickstart 5 for Duende IdentityServer! In this quickstart you will
Expand Down

0 comments on commit e0e7d81

Please sign in to comment.