Skip to content

Commit 7e7921a

Browse files
authored
Merge pull request #976 from DuendeSoftware/mb/resource-isolation
Document resource parameter usage and isolation enhancements in IdentityServer
2 parents d8826e9 + 679fe86 commit 7e7921a

1 file changed

Lines changed: 99 additions & 18 deletions

File tree

  • src/content/docs/identityserver/fundamentals/resources

src/content/docs/identityserver/fundamentals/resources/isolation.md

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,34 @@ redirect_from:
1414
This is an Enterprise Edition feature.
1515
:::
1616

17-
OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view.
17+
OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view.
1818
This means that all the requested scope and audience combination get merged into a single access token.
19+
1920
This has a couple of downsides:
2021

21-
* tokens can become very powerful (and big)
22-
* if such a token leaks, it allows access to multiple resources
23-
* resources within that single token might have conflicting settings, e.g.
24-
* user claims of all resources share the same token
25-
* resource specific processing like signing or encryption algorithms conflict
26-
* without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly
22+
* Tokens can become very powerful (and large)
23+
* If such a token leaks, it allows access to multiple resources
24+
* Resources within that single token might have conflicting settings, e.g.
25+
* User claims of all resources share the same token
26+
* Resource-specific processing like signing or encryption algorithms conflict
27+
* Without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly
28+
29+
### Audience Ambiguity
30+
31+
In a system with multiple APIs (e.g., Shipping, Invoicing and Inventory APIs), a single token often lists all of them as valid audiences.
32+
33+
```json
34+
{
35+
"iss": "https://demo.duendesoftware.com",
36+
"aud": ["invoice_api", "shipping_api", "inventory_api"],
37+
"scope": ["invoice.read", "shipping.write", "inventory.read"]
38+
}
39+
```
40+
41+
This violates the Principle of Least Privilege. If this token is leaked from the Inventory API, it can be used to call the Invoice API.
2742

28-
To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called `resource`.
29-
This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single
43+
To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called `resource`.
44+
This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single
3045
value only, and all scopes belong to that single resource).
3146

3247
## Using The Resource Parameter
@@ -52,7 +67,8 @@ If the client would request a token for the `read` scope, the resulting access t
5267
the invoice and the products API and thus be accepted at both APIs.
5368

5469
### Machine to Machine Scenarios
55-
If the client in addition passes the `resource` parameter specifying the name of the resource where it wants to use
70+
71+
If the client in addition passes the `resource` parameter specifying the name of the resource where it wants to use
5672
the access token, the token engine can `down-scope` the resulting access token to the single resource, e.g.:
5773

5874
```text
@@ -70,13 +86,14 @@ Thus resulting in an access token like this (some details omitted):
7086

7187
```json
7288
{
73-
"aud": [ "urn:invoice" ],
74-
"scope": "read",
75-
"client_id": "client"
89+
"aud": ["urn:invoice"],
90+
"scope": "read",
91+
"client_id": "client"
7692
}
7793
```
7894

7995
### Interactive Applications
96+
8097
The authorize endpoint supports the `resource` parameter as well, e.g.:
8198

8299
```text
@@ -98,6 +115,7 @@ resource=urn:invoices
98115
```
99116

100117
### Requesting Access To Multiple Resources
118+
101119
It is also possible to request access to multiple resources. This will result in multiple access tokens - one for each request resource.
102120

103121
```text
@@ -135,7 +153,8 @@ resource=urn:products
135153
The end-result will be that the client has two access tokens - one for each resource and can manage their lifetime via the refresh token.
136154

137155
## Enforcing Resource Isolation
138-
All examples so far used the `resource` parameter optionally. If you have API resources, where you want to make sure
156+
157+
All examples so far used the `resource` parameter optionally. If you have API resources, where you want to make sure
139158
they are not sharing access tokens with other resources, you can enforce the resource indicator, e.g.:
140159

141160
```csharp title="ApiResources.cs" {6,12}
@@ -156,17 +175,79 @@ var resources = new[]
156175
```
157176

158177
The `RequireResourceIndicator` property **does not** mean that clients are forced to send the `resource` parameter when
159-
they request scopes associated with the API resource. You can still request those scopes without setting the `resource`
160-
parameter (or including the resource), and IdentityServer will issue a token as long as the client is allowed to request
178+
they request scopes associated with the API resource. You can still request those scopes without setting the `resource`
179+
parameter (or including the resource), and IdentityServer will issue a token as long as the client is allowed to request
161180
the scopes.
162181

163-
Instead, `RequireResourceIndicator` controls **when** the resource's URI is included in the **audience claim** (`aud`)
182+
Instead, `RequireResourceIndicator` controls **when** the resource's URI is included in the **audience claim** (`aud`)
164183
of the issued access token.
165184

166185
* When `RequireResourceIndicator` is `false` (the default):
167186
IdentityServer **automatically includes** the API's resource URI in the token's audience if any of the resource's scopes
168187
are requested, even if the `resource` parameter was not sent in the request or didn't contain the resource URI.
169188
* When `RequireResourceIndicator` is `true`:
170-
The API's resource URI will **only** be included in the audience **if the client explicitly includes the resource URI**
189+
The API's resource URI will **only** be included in the audience **if the client explicitly includes the resource URI**
171190
via the `resource` parameter when requesting the token.
172191

192+
## .NET Client Implementation
193+
194+
While the examples above show the underlying HTTP protocol, .NET clients can use the Duende libraries to handle resource indicators easily.
195+
196+
### Machine-to-Machine (Worker)
197+
198+
When using `Duende.IdentityModel` for client credentials, you can pass the `resource` parameter using the `Parameters` dictionary:
199+
200+
```csharp
201+
using Duende.IdentityModel.Client;
202+
203+
var client = new HttpClient();
204+
205+
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
206+
{
207+
Address = "https://demo.duendesoftware.com/connect/token",
208+
ClientId = "invoice_worker",
209+
ClientSecret = "secret",
210+
211+
// The scope defines the permission
212+
Scope = "invoice.read",
213+
214+
// The parameter defines the target (RFC 8707)
215+
Parameters = { { "resource", "urn:invoices" } }
216+
});
217+
```
218+
219+
### ASP.NET Core
220+
221+
For interactive applications using the standard OpenID Connect handler, use the `Resource` property on `OpenIdConnectOptions`:
222+
223+
```csharp
224+
.AddOpenIdConnect(options =>
225+
{
226+
options.Authority = "https://demo.duendesoftware.com";
227+
options.ClientId = "interactive_app";
228+
229+
options.Scope.Add("invoice.read");
230+
231+
// Explicitly set the target resource here
232+
options.Resource = "urn:invoices";
233+
234+
options.ResponseType = "code";
235+
options.SaveTokens = true;
236+
});
237+
```
238+
239+
Note that while the RFC allows multiple `resource` parameters, the Microsoft OpenID Connect handler only supports a single resource value here.
240+
241+
For dynamic scenarios (e.g. multi-tenant), you can set the resource parameter in the `OnRedirectToIdentityProvider` event:
242+
243+
```csharp
244+
options.Events.OnRedirectToIdentityProvider = context =>
245+
{
246+
var tenantSpecificResource = DetermineResource(context);
247+
248+
// Overwrite or set the 'resource' parameter
249+
context.ProtocolMessage.SetParameter("resource", tenantSpecificResource);
250+
251+
return Task.CompletedTask;
252+
};
253+
```

0 commit comments

Comments
 (0)