Skip to content

Commit 82253d6

Browse files
committed
Update readmes to be inline with package contents
1 parent 8c8cb93 commit 82253d6

File tree

2 files changed

+545
-146
lines changed

2 files changed

+545
-146
lines changed

src/CSharp/README.md

Lines changed: 342 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,342 @@
1-
# AuthZenClient
1+
# AuthZen C# Client
2+
3+
A C# client library for interacting with [AuthZen](https://openid.github.io/authzen/)-compliant Policy Decision Points (PDPs). This library implements the AuthZen Authorization API 1.0 specification.
4+
5+
## Features
6+
7+
- **Discovery** - Automatic PDP configuration via `/.well-known/authzen-configuration`
8+
- **Access Evaluation API** - Single authorization decisions
9+
- **Access Evaluations API** - Batch (boxcar) authorization decisions with multiple evaluation semantics
10+
- **Fluent Builders** - Builder pattern for constructing evaluation requests
11+
- **Property Bags** - Flexible key-value properties on subjects, resources, actions, and context
12+
- **Compatibility** - Targets .NET Standard 2.0 for broad runtime support
13+
14+
## Installation
15+
16+
```bash
17+
dotnet add package Rsk.AuthZen.Client
18+
```
19+
20+
Or via the NuGet Package Manager:
21+
22+
```
23+
Install-Package Rsk.AuthZen.Client
24+
```
25+
26+
## Quick Start
27+
28+
The client uses AuthZen discovery to automatically resolve evaluation endpoints. On the first call to `Evaluate()`, the client fetches `/.well-known/authzen-configuration` from the authorization URL and caches the result.
29+
30+
### Registration
31+
32+
Register the client in your dependency injection container:
33+
34+
```csharp
35+
services.AddHttpClient();
36+
services.Configure<AuthZenClientOptions>(options =>
37+
{
38+
options.AuthorizationUrl = "https://pdp.mycompany.com";
39+
});
40+
services.AddTransient<IAuthZenClient, AuthZenClient>();
41+
```
42+
43+
### Basic Evaluation
44+
45+
```csharp
46+
// Build a single evaluation request using the fluent builder
47+
var request = new AuthZenSingleRequestBuilder()
48+
.SetSubject("alice@example.com", "user")
49+
.SetAction("can_read")
50+
.SetResource("123", "document")
51+
.Build();
52+
53+
// Evaluate
54+
AuthZenResponse response = await authZenClient.Evaluate(request);
55+
56+
if (response.Decision == Decision.Permit)
57+
{
58+
Console.WriteLine("Access granted");
59+
}
60+
else
61+
{
62+
Console.WriteLine("Access denied");
63+
}
64+
```
65+
66+
## API Reference
67+
68+
### Client Configuration
69+
70+
The client is configured via `AuthZenClientOptions` and uses `IHttpClientFactory` for HTTP requests:
71+
72+
```csharp
73+
public class AuthZenClientOptions
74+
{
75+
/// <summary>
76+
/// Base URL of the AuthZen authorization service (required).
77+
/// </summary>
78+
public string AuthorizationUrl { get; set; }
79+
}
80+
```
81+
82+
The `AuthZenClient` constructor requires:
83+
84+
| Parameter | Type | Description |
85+
|---|---|---|
86+
| `httpClientFactory` | `IHttpClientFactory` | Factory for creating `HttpClient` instances |
87+
| `options` | `IOptions<AuthZenClientOptions>` | Configuration options |
88+
89+
### Discovery
90+
91+
Fetch the PDP's AuthZen configuration from `/.well-known/authzen-configuration`. This is called automatically before the first evaluation, but you can also call it explicitly:
92+
93+
```csharp
94+
AuthZenMetadataResponse metadata = await authZenClient.GetMetadata();
95+
96+
Console.WriteLine(metadata.PolicyDecisionPoint);
97+
Console.WriteLine(metadata.AccessEvaluationEndpoint);
98+
Console.WriteLine(metadata.AccessEvaluationsEndpoint);
99+
```
100+
101+
The returned `AuthZenMetadataResponse` contains:
102+
103+
| Property | Type | Description |
104+
|---|---|---|
105+
| `PolicyDecisionPoint` | `string` | Base URL of the PDP (required) |
106+
| `AccessEvaluationEndpoint` | `string` | Single evaluation endpoint (required) |
107+
| `AccessEvaluationsEndpoint` | `string` | Batch evaluations endpoint |
108+
| `SearchSubjectEndpoint` | `string` | Subject search endpoint |
109+
| `SearchResourceEndpoint` | `string` | Resource search endpoint |
110+
| `SearchActionEndpoint` | `string` | Action search endpoint |
111+
112+
### Single Access Evaluation
113+
114+
Build and evaluate a single authorization request using `AuthZenSingleRequestBuilder`:
115+
116+
```csharp
117+
var request = new AuthZenSingleRequestBuilder()
118+
.SetCorrelationId("req-12345")
119+
.SetSubject("alice@example.com", "user")
120+
.Add("department", "Sales")
121+
.Add("role", "Manager")
122+
.SetAction("can_read")
123+
.Add("method", "GET")
124+
.SetResource("123", "document")
125+
.Add("classification", "confidential")
126+
.SetContext()
127+
.Add("location", "office")
128+
.Add("time", DateTime.UtcNow.ToString("o"))
129+
.Build();
130+
131+
AuthZenResponse response = await authZenClient.Evaluate(request);
132+
133+
Console.WriteLine($"Decision: {response.Decision}"); // Permit or Deny
134+
Console.WriteLine($"Correlation: {response.CorrelationId}");
135+
Console.WriteLine($"Context: {response.Context}");
136+
```
137+
138+
The builder methods `SetSubject()`, `SetAction()`, `SetResource()`, and `SetContext()` each return an `IAuthZenPropertyBag`, allowing you to chain `.Add(name, value)` calls to attach additional properties.
139+
140+
### Response
141+
142+
The `AuthZenResponse` contains:
143+
144+
| Property | Type | Description |
145+
|---|---|---|
146+
| `Decision` | `Decision` | `Decision.Permit` or `Decision.Deny` |
147+
| `Context` | `string` | Context information from the PDP response |
148+
| `CorrelationId` | `string` | Request correlation ID from the `X-Request-ID` header |
149+
150+
### Batch (Boxcar) Access Evaluations
151+
152+
Evaluate multiple authorization requests in a single call using `AuthZenBoxcarRequestBuilder`:
153+
154+
```csharp
155+
var request = new AuthZenBoxcarRequestBuilder()
156+
.SetCorrelationId("batch-001")
157+
// Default values applied to any evaluation that omits the field
158+
.SetDefaultSubject("alice@example.com", "user")
159+
.SetDefaultAction("can_read")
160+
// Individual evaluations
161+
.AddRequest()
162+
.SetResource("doc-1", "document")
163+
.AddRequest()
164+
.SetResource("doc-2", "document")
165+
.SetAction("can_write") // Overrides the default action
166+
.AddRequest()
167+
.SetSubject("bob@example.com", "user") // Overrides the default subject
168+
.SetResource("doc-3", "document")
169+
.Build();
170+
171+
AuthZenBoxcarResponse response = await authZenClient.Evaluate(request);
172+
173+
foreach (var evaluation in response.Evaluations)
174+
{
175+
Console.WriteLine($"Decision: {evaluation.Decision}");
176+
}
177+
```
178+
179+
#### Default Values
180+
181+
The boxcar builder supports setting default values for subject, resource, action, and context via `SetDefaultSubject()`, `SetDefaultResource()`, `SetDefaultAction()`, and `SetDefaultContext()`. These defaults are applied to any individual evaluation that does not specify that field. Individual evaluations override defaults when both are supplied.
182+
183+
#### Fallback Behaviour
184+
185+
If a boxcar request contains no individual evaluations (only defaults), the client automatically falls back to a single evaluation using the default values.
186+
187+
If the PDP does not advertise a batch evaluations endpoint (`AccessEvaluationsEndpoint` is missing from discovery), the client throws a `NotSupportedException`.
188+
189+
### Evaluation Semantics
190+
191+
The batch evaluation API supports three evaluation semantics via `SetEvaluationSemantics()`:
192+
193+
```csharp
194+
var request = new AuthZenBoxcarRequestBuilder()
195+
.SetDefaultSubject("alice@example.com", "user")
196+
.SetEvaluationSemantics(BoxcarSemantics.DenyOnFirstDeny)
197+
.AddRequest()
198+
.SetAction("can_read")
199+
.SetResource("doc-1", "document")
200+
.AddRequest()
201+
.SetAction("can_read")
202+
.SetResource("doc-2", "document")
203+
.Build();
204+
```
205+
206+
| Semantic | Description |
207+
|---|---|
208+
| `BoxcarSemantics.ExecuteAll` | Execute all evaluations and return all results (default) |
209+
| `BoxcarSemantics.DenyOnFirstDeny` | Stop and return on the first denial (short-circuit AND) |
210+
| `BoxcarSemantics.PermitOnFirstPermit` | Stop and return on the first permit (short-circuit OR) |
211+
212+
## Error Handling
213+
214+
The client throws `AuthZenRequestFailureException` when HTTP requests to the PDP fail:
215+
216+
```csharp
217+
try
218+
{
219+
var response = await authZenClient.Evaluate(request);
220+
}
221+
catch (AuthZenRequestFailureException ex)
222+
{
223+
// HTTP error from the PDP (e.g. 400, 401, 500)
224+
Console.Error.WriteLine($"AuthZen request failed: {ex.Message}");
225+
}
226+
catch (NotSupportedException ex)
227+
{
228+
// Batch endpoint not supported by this PDP
229+
Console.Error.WriteLine($"Not supported: {ex.Message}");
230+
}
231+
```
232+
233+
The client also implements automatic retry on 404 responses — if an evaluation endpoint returns 404, the client re-fetches the discovery metadata and retries once.
234+
235+
## Advanced Examples
236+
237+
### Rich Context Evaluation
238+
239+
```csharp
240+
var request = new AuthZenSingleRequestBuilder()
241+
.SetSubject("alice@example.com", "user")
242+
.Add("department", "Sales")
243+
.Add("role", "Manager")
244+
.Add("clearance_level", "confidential")
245+
.SetAction("can_read")
246+
.Add("method", "GET")
247+
.Add("api_endpoint", "/documents/123")
248+
.SetResource("123", "document")
249+
.Add("owner", "bob@example.com")
250+
.Add("classification", "confidential")
251+
.Add("project", "Project Alpha")
252+
.SetContext()
253+
.Add("location", "office")
254+
.Add("device_type", "laptop")
255+
.Add("ip_address", "192.168.1.100")
256+
.Add("time", DateTime.UtcNow.ToString("o"))
257+
.Build();
258+
259+
AuthZenResponse response = await authZenClient.Evaluate(request);
260+
```
261+
262+
### Batch Evaluation with Short-Circuit Logic
263+
264+
```csharp
265+
var request = new AuthZenBoxcarRequestBuilder()
266+
.SetDefaultSubject("alice@example.com", "user")
267+
.SetEvaluationSemantics(BoxcarSemantics.DenyOnFirstDeny)
268+
.AddRequest()
269+
.SetAction("can_read")
270+
.SetResource("1", "document")
271+
.AddRequest()
272+
.SetAction("can_read")
273+
.SetResource("2", "document")
274+
.AddRequest()
275+
.SetAction("can_read")
276+
.SetResource("3", "document")
277+
.Build();
278+
279+
AuthZenBoxcarResponse response = await authZenClient.Evaluate(request);
280+
Console.WriteLine($"Evaluated {response.Evaluations.Count} requests");
281+
```
282+
283+
### Batch Evaluation with Mixed Defaults
284+
285+
```csharp
286+
var request = new AuthZenBoxcarRequestBuilder()
287+
// Defaults applied to evaluations that omit the field
288+
.SetDefaultSubject("default-user@company.com", "user")
289+
.SetDefaultResource("shared-document", "document")
290+
.SetDefaultAction("read")
291+
.SetDefaultContext()
292+
.Add("environment", "production")
293+
// Override subject only
294+
.AddRequest()
295+
.SetSubject("alice@company.com", "user")
296+
// Override resource and action
297+
.AddRequest()
298+
.SetResource("user-service", "api")
299+
.SetAction("execute")
300+
.Build();
301+
302+
AuthZenBoxcarResponse response = await authZenClient.Evaluate(request);
303+
```
304+
305+
## Compatibility
306+
307+
The library targets **.NET Standard 2.0**, which is supported by:
308+
309+
- .NET Core 2.0+
310+
- .NET 5+
311+
- .NET Framework 4.6.1+
312+
313+
### Dependencies
314+
315+
| Package | Version |
316+
|---|---|
317+
| `Microsoft.Extensions.Http` | 9.0.3 |
318+
| `Microsoft.Extensions.Options` | 9.0.3 |
319+
| `System.Text.Json` | 9.0.3 |
320+
321+
## Development
322+
323+
### Building
324+
325+
```bash
326+
dotnet build
327+
```
328+
329+
### Testing
330+
331+
```bash
332+
dotnet test
333+
```
334+
335+
## License
336+
337+
See LICENSE file for details.
338+
339+
## Related
340+
341+
- [AuthZen Specification](https://openid.github.io/authzen/)
342+
- [OpenID Foundation](https://openid.net/)

0 commit comments

Comments
 (0)