Skip to content

Commit 5fb4165

Browse files
authored
Merge pull request #978 from DuendeSoftware/mb/standardize
Introduce examples using minimal APIs where ever API controllers are used
2 parents ef0227a + 84adf4f commit 5fb4165

5 files changed

Lines changed: 65 additions & 97 deletions

File tree

src/content/docs/bff/fundamentals/apis/local.mdx

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,24 @@ Your Embedded endpoints can leverage services like the HTTP client factory and D
3737
The following is a simplified example showing how Embedded endpoints can get managed access tokens and use them to make requests to remote APIs.
3838

3939
```csharp
40-
// MyApiController.cs
41-
[Route("myApi")]
42-
public class MyApiController : ControllerBase
40+
// Program.cs
41+
app.MapGet("/myApi", async (IHttpClientFactory httpClientFactory, HttpContext context) =>
4342
{
44-
private readonly IHttpClientFactory _httpClientFactory;
45-
46-
public MyApiController(IHttpClientFactory httpClientFactory)
47-
{
48-
_httpClientFactory = httpClientFactory;
49-
}
43+
var id = context.Request.Query["id"];
5044

51-
public async Task<IActionResult> Get(string id)
52-
{
53-
// create HTTP client
54-
var client = _httpClientFactory.CreateClient();
45+
// create HTTP client
46+
var client = httpClientFactory.CreateClient();
5547

56-
// get current user access token and set it on HttpClient
57-
var token = await HttpContext.GetUserAccessTokenAsync();
58-
client.SetBearerToken(token);
48+
// get current user access token and set it on HttpClient
49+
var token = await context.GetUserAccessTokenAsync();
50+
client.SetBearerToken(token);
5951

60-
// call remote API
61-
var response = await client.GetAsync($"https://remoteServer/remoteApi?id={id}");
52+
// call remote API
53+
var response = await client.GetAsync($"https://remoteServer/remoteApi?id={id}");
6254

63-
// maybe process response and return to frontend
64-
return new JsonResult(await response.Content.ReadAsStringAsync());
65-
}
66-
}
55+
// maybe process response and return to frontend
56+
return Results.Text(await response.Content.ReadAsStringAsync());
57+
});
6758
```
6859

6960
The example above is simplified to demonstrate the way that you might obtain a token. Embedded endpoints will typically enforce constraints on the way the API is called, aggregate multiple calls, or perform other business logic. Embedded endpoints that merely forward requests from the frontend to the remote API may not be needed at all. Instead, you could proxy the requests through the BFF using either the [simple http forwarder](/bff/fundamentals/apis/remote.mdx) or [YARP](/bff/fundamentals/apis/yarp.md).

src/content/docs/bff/fundamentals/tokens.md

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ Duende.BFF includes an automatic token management feature. This uses the access
1515

1616
For most scenarios, there is no additional configuration necessary. The token management will infer the configuration and token endpoint URL from the metadata of the OpenID Connect provider.
1717

18-
The easiest way to retrieve the current access token is to use an extension method on *HttpContext*:
18+
The easiest way to retrieve the current access token is to use an extension method on `HttpContext`:
1919

2020
```csharp
2121
var token = await HttpContext.GetUserAccessTokenAsync();
2222
```
2323

24-
You can then use the token to set it on an *HttpClient* instance:
24+
You can then use the token to set it on an `HttpClient`instance:
2525

2626
```csharp
2727
var client = new HttpClient();
2828
client.SetBearerToken(token);
2929
```
3030

31-
We recommend to leverage the *HttpClientFactory* to fabricate HTTP clients that are already aware of the token management plumbing. For this you would register a named client in your application startup e.g. like this:
31+
We recommend to use the `HttpClientFactory` to create HTTP clients that are already aware of the token management plumbing. For this you would register a named client in your application startup e.g. like this:
3232

3333
```csharp
3434
// Program.cs
@@ -42,27 +42,16 @@ builder.Services.AddUserAccessTokenHttpClient("apiClient", configureClient: clie
4242
And then retrieve a client instance like this:
4343

4444
```csharp
45-
[Route("myApi")]
46-
public class MyApiController : ControllerBase
45+
app.MapGet("/myApi", async (IHttpClientFactory httpClientFactory, HttpContext context) =>
4746
{
48-
private readonly IHttpClientFactory _httpClientFactory;
49-
50-
public MyController(IHttpClientFactory httpClientFactory)
51-
{
52-
_httpClientFactory = httpClientFactory;
53-
}
54-
55-
public async Task<IActionResult> Get(string id)
56-
{
57-
// create HTTP client with automatic token management
58-
var client = _httpClientFactory.CreateClient("apiClient");
59-
60-
// call remote API
61-
var response = await client.GetAsync("remoteApi");
62-
63-
// rest omitted
64-
}
65-
}
47+
// create HTTP client with automatic token management
48+
var client = httpClientFactory.CreateClient("apiClient");
49+
50+
// call remote API
51+
var response = await client.GetAsync("remoteApi");
52+
53+
// rest omitted
54+
});
6655
```
6756

6857
If you prefer to use typed clients, you can do that as well:
@@ -75,25 +64,26 @@ services.AddHttpClient<MyTypedApiClient>(client =>
7564
}).AddUserAccessTokenHandler();
7665
```
7766

78-
And then use that client, for example like this on a controller's action method:
67+
And then use that client, for example like this on an endpoint:
7968

8069
```csharp
81-
public async Task<IActionResult> CallApiAsUserTyped(
82-
[FromServices] MyTypedClient client)
70+
app.MapGet("/myApi", async (MyTypedClient client) =>
8371
{
8472
var response = await client.GetData();
85-
73+
8674
// rest omitted
87-
}
75+
});
8876
```
8977

90-
The client will internally always try to use a current and valid access token. If for any reason this is not possible, the 401 status code will be returned to the caller.
78+
The client will internally always try to use a current and valid access token. If for any reason this is not possible, the 401 status code will be returned to the caller.
9179

9280
### Reuse of Refresh Tokens
93-
We recommend that you configure IdentityServer to issue reusable refresh tokens to BFF clients. Because the BFF is a confidential client, it does not need one-time use refresh tokens. Reusable refresh tokens are desirable because they avoid performance and user experience problems associated with one time use tokens. See the discussion on [rotating refresh tokens](/identityserver/tokens/refresh.md) and the [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.2.2) for more details.
81+
82+
We recommend that you configure IdentityServer to issue reusable refresh tokens to BFF clients. Because the BFF is a confidential client, it does not need one-time use refresh tokens. Reusable refresh tokens are desirable because they avoid performance and user experience problems associated with one time use tokens. See the discussion on [rotating refresh tokens](/identityserver/tokens/refresh.md) and the [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.2.2) for more details.
9483

9584
### Manually revoking refresh tokens
96-
Duende.BFF revokes refresh tokens automatically at logout time. This behavior can be disabled with the *RevokeRefreshTokenOnLogout* option.
85+
86+
Duende.BFF revokes refresh tokens automatically at logout time. This behavior can be disabled with the _RevokeRefreshTokenOnLogout_ option.
9787

9888
If you want to manually revoke the current refresh token, you can use the following code:
9989

src/content/docs/identityserver/apis/aspnetcore/authorization.md

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,40 +42,29 @@ builder.Services.AddAuthorization(options =>
4242
app.MapControllers().RequireAuthorization("read_access");
4343
```
4444

45-
...or imperatively inside the controller:
45+
...or imperatively inside the endpoint handler:
4646

4747
```cs
48-
public class DataController : ControllerBase
48+
app.MapGet("/", async (IAuthorizationService authz, ClaimsPrincipal user) =>
4949
{
50-
IAuthorizationService _authz;
50+
var allowed = await authz.AuthorizeAsync(user, "read_access");
5151

52-
public DataController(IAuthorizationService authz)
52+
if (!allowed.Succeeded)
5353
{
54-
_authz = authz;
54+
return Results.Forbid();
5555
}
5656

57-
public async Task<IActionResult> Get()
58-
{
59-
var allowed = _authz.CheckAccess(User, "read_access");
60-
61-
// rest omitted
62-
}
63-
}
57+
// rest omitted
58+
});
6459
```
6560

6661
... or declaratively:
6762

6863
```cs
69-
public class DataController : ControllerBase
64+
app.MapGet("/", () =>
7065
{
71-
[Authorize("read_access")]
72-
public async Task<IActionResult> Get()
73-
{
74-
var allowed = authz.CheckAccess(User, "read_access");
75-
76-
// rest omitted
77-
}
78-
}
66+
// rest omitted
67+
}).RequireAuthorization("read_access");
7968
```
8069

8170
#### Scope Claim Format

src/content/docs/identityserver/apis/index.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var scopes = new List<ApiScope>
4545
new Client
4646
{
4747
// rest omitted
48-
AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName },
48+
AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName },
4949
}
5050
```
5151

@@ -60,6 +60,15 @@ To enable token validation for local APIs, add the following to your IdentitySer
6060
builder.Services.AddLocalApiAuthentication();
6161
```
6262

63+
To protect an API endpoint, call `RequireAuthorization` with the `LocalApi.PolicyName` policy:
64+
65+
```cs
66+
app.MapGet("/localApi", () =>
67+
{
68+
// omitted
69+
}).RequireAuthorization(LocalApi.PolicyName);
70+
```
71+
6372
To protect an API controller, decorate it with an `Authorize` attribute using the `LocalApi.PolicyName` policy:
6473

6574
```cs
@@ -77,6 +86,7 @@ public class LocalApiController : ControllerBase
7786
Authorized clients can then request a token for the `IdentityServerApi` scope and use it to call the API.
7887

7988
## Discovery
89+
8090
You can also add your endpoints to the discovery document if you want, e.g.like this::
8191

8292
```cs
@@ -88,16 +98,17 @@ builder.Services.AddIdentityServer(options =>
8898
```
8999

90100
## Advanced
91-
Under the covers, the `AddLocalApiAuthentication` helper does a couple of things:
101+
102+
Under the hood, the `AddLocalApiAuthentication` helper does a couple of things:
92103

93104
* adds an authentication handler that validates incoming tokens using IdentityServer's built-in token validation engine (the name of this handler is `IdentityServerAccessToken` or `IdentityServerConstants.LocalApi.AuthenticationScheme`
94105
* configures the authentication handler to require a scope claim inside the access token of value `IdentityServerApi`
95106
* sets up an authorization policy that checks for a scope claim of value `IdentityServerApi`
96107

97108
This covers the most common scenarios. You can customize this behavior in the following ways:
98109

99-
* Add the authentication handler yourself by calling `services.AddAuthentication().AddLocalApi(...)`
100-
* this way you can specify the required scope name yourself, or (by specifying no scope at all) accept any token from the current IdentityServer instance
110+
* Add the authentication handler yourself by calling `services.AddAuthentication().AddLocalApi(...)`.
111+
This way you can specify the required scope name yourself, or (by specifying no scope at all) accept any token from the current IdentityServer instance
101112
* Do your own scope validation/authorization in your controllers using custom policies or code, e.g.:
102113

103114

@@ -115,6 +126,7 @@ builder.Services.AddAuthorization(options =>
115126
```
116127

117128
## Claims Transformation
129+
118130
You can provide a callback to transform the claims of the incoming token after validation.
119131
Either use the helper method, e.g.:
120132

@@ -129,5 +141,3 @@ builder.Services.AddLocalApiAuthentication(principal =>
129141
```
130142

131143
...or implement the event on the options if you add the authentication handler manually.
132-
133-

src/content/docs/identityserver/tokens/internal.md

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,18 @@ Sometimes, extensibility code running on your IdentityServer needs access tokens
1515
not necessary to use the protocol endpoints. The tokens can be issued internally.
1616

1717
`IIdentityServerTools` is a collection of useful internal tools that you might need when writing extensibility code
18-
for IdentityServer. To use it, inject it into your code, e.g. a controller::
18+
for IdentityServer. To use it, inject it into your code, e.g. an endpoint:
1919

20-
```cs
21-
public MyController(IIdentityServerTools tools)
20+
```csharp
21+
app.MapGet("/myAction", async (IIdentityServerTools tools) =>
2222
{
23-
_tools = tools;
24-
}
25-
```
26-
27-
The `IssueJwtAsync` method allows creating JWT tokens using the IdentityServer token creation engine. The
28-
`IssueClientJwtAsync` is an easier
29-
version of that for creating tokens for server-to-server communication (e.g. when you have to call an IdentityServer
30-
protected API from your code):
31-
32-
```cs
33-
public async Task<IActionResult> MyAction()
34-
{
35-
var token = await _tools.IssueClientJwtAsync(
23+
var token = await tools.IssueClientJwtAsync(
3624
clientId: "client_id",
3725
lifetime: 3600,
3826
audiences: new[] { "backend.api" });
3927

4028
// more code
41-
}
29+
});
4230
```
4331

4432
The `IIdentityServerTools` interface was added in v7 to allow mocking. Previous versions referenced the

0 commit comments

Comments
 (0)