Skip to content

Commit 009f162

Browse files
glucaciCopilot
andcommitted
Refactor Bewit token extraction; unify extraction options, update middleware integration, and enhance configuration support in appsettings.json
Co-authored-by: Copilot <copilot@github.com>
1 parent 2fba15c commit 009f162

8 files changed

Lines changed: 140 additions & 29 deletions

File tree

README.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,64 @@ public static async Task<NominationDto> AssignNomineeAsync(
298298
### Setup
299299

300300
```csharp
301-
app.UseBewitTokenHeaderExtraction();
301+
app.UseBewitTokenExtraction();
302302
```
303303

304+
## HTTP Endpoint Integration
305+
306+
```bash
307+
dotnet add package Bewit.Http
308+
```
309+
310+
Protect minimal API or middleware-based endpoints:
311+
```csharp
312+
app.UseBewitEndpointAuthorization<MyPayload>();
313+
```
314+
315+
The middleware validates the token from the configured header or query parameter and makes the payload available via `GetBewitPayload<T>()`.
316+
317+
## Token Extraction
318+
319+
All extensions (HotChocolate, Http, Mvc) read the bewit token from the same configurable sources: an HTTP **header** and/or a **query parameter**. Header takes precedence when both are present.
320+
321+
Defaults match the v6.x behavior:
322+
- Header: `bewitToken`
323+
- Query parameter: `bewit`
324+
325+
### Code configuration
326+
```csharp
327+
services.AddBewit(bewit =>
328+
{
329+
bewit.ConfigureTokenExtraction(o =>
330+
{
331+
o.HeaderName = "X-Custom-Token";
332+
o.QueryParamName = "token";
333+
});
334+
335+
bewit.AddPayload<string>();
336+
});
337+
```
338+
339+
### appsettings.json
340+
```json
341+
{
342+
"Bewit:TokenExtraction": {
343+
"HeaderName": "X-Custom-Token",
344+
"QueryParamName": "token"
345+
}
346+
}
347+
```
348+
349+
```csharp
350+
services.AddBewit(bewit =>
351+
{
352+
bewit.BindTokenExtractionConfiguration("Bewit:TokenExtraction");
353+
bewit.AddPayload<string>();
354+
});
355+
```
356+
357+
Both can be combined — `BindTokenExtractionConfiguration` loads from appsettings first, then `ConfigureTokenExtraction` overrides specific values (standard .NET options layering).
358+
304359
## MVC Integration
305360

306361
```bash
@@ -325,9 +380,9 @@ public IActionResult Download(string id) { ... }
325380
| `Bewit.Extensions.Mvc` | MVC filters and parameter binding |
326381
| `Bewit.Http` | Minimal API endpoint authorization |
327382

328-
## Migration from v1.x
383+
## Migration
329384

330-
See [Migration Guide](docs/migration-guide.md).
385+
See [Migration Guide v7](docs/migration-guide-v7.md).
331386

332387
## Community
333388

docs/migration-guide-v7.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
| MongoDB Driver | 2.x | 3.0+ |
1717
| HttpContextAccessor | Manual `services.AddHttpContextAccessor()` | Auto-registered by `AddBewit()` |
1818
| Startup validation | None | `ServerControlled` without nonce repo fails at startup |
19+
| Token extraction | Hardcoded header/query per extension | Unified `BewitTokenExtractionOptions` with header + query fallback |
20+
| HotChocolate setup | `UseBewitTokenHeaderExtraction()` | `UseBewitTokenExtraction()` |
1921

2022
## DI Registration Migration
2123

@@ -245,3 +247,55 @@ services.AddBewit(bewit =>
245247
| `PayloadBuilder.UseSlidingWindow(TimeSpan)` | `ConfigureOptions(o => o.SlidingWindow = ...)` |
246248
| `PayloadBuilder.WithTokenDuration(TimeSpan)` | `ConfigureOptions(o => o.TokenDuration = ...)` |
247249
| `UseMongoPersistence(config, ...)` | `UseMongoDb(...)` on `BewitBuilder` or `PayloadBuilder<T>` |
250+
| `BewitTokenConstants` | `BewitTokenExtractionOptions` (configurable via options pattern) |
251+
| `UseBewitTokenHeaderExtraction()` | `UseBewitTokenExtraction()` |
252+
253+
## Token Extraction Migration
254+
255+
In v6.x, the HotChocolate extension used a hardcoded `bewitToken` header and the Http extension used a hardcoded `bewit` query parameter. These were not configurable.
256+
257+
In v7.0, all extensions (HotChocolate, Http, Mvc) use `BewitTokenExtractionOptions` — a shared, configurable options class that follows the standard .NET options pattern.
258+
259+
### Before (v6.x)
260+
```csharp
261+
// HotChocolate — header only, hardcoded name
262+
app.UseBewitTokenHeaderExtraction();
263+
264+
// Http — query param only, hardcoded name
265+
app.UseBewitEndpointAuthorization<T>();
266+
267+
// Mvc — query param only, hardcoded name
268+
[BewitUrlAuthorization]
269+
```
270+
271+
### After (v7.0)
272+
```csharp
273+
// HotChocolate — reads header first, then query param
274+
app.UseBewitTokenExtraction();
275+
276+
// Http — reads header first, then query param
277+
app.UseBewitEndpointAuthorization<T>();
278+
279+
// Mvc — reads header first, then query param
280+
[BewitUrlAuthorization]
281+
```
282+
283+
All three now check **header first, then fall back to query parameter**. Default names are unchanged (`bewitToken` header, `bewit` query param), so existing consumers work without config changes.
284+
285+
### Custom token extraction
286+
```csharp
287+
services.AddBewit(bewit =>
288+
{
289+
bewit.ConfigureTokenExtraction(o =>
290+
{
291+
o.HeaderName = "X-Custom-Token";
292+
o.QueryParamName = "token";
293+
});
294+
// or from appsettings.json:
295+
bewit.BindTokenExtractionConfiguration("Bewit:TokenExtraction");
296+
297+
bewit.AddPayload<string>();
298+
});
299+
```
300+
301+
`BewitTokenExtractionOptions` supports full .NET options layering: `Bind``Configure``PostConfigure`, with `ValidateDataAnnotations` and `ValidateOnStart`.

src/Core/BewitBuilder.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public sealed class BewitBuilder
1414

1515
internal Action<BewitTokenExtractionOptions>? TokenExtractionAction { get; private set; }
1616

17+
internal string? TokenExtractionConfigSection { get; private set; }
18+
1719
internal List<Action<IServiceCollection>> PayloadRegistrations { get; } = [];
1820

1921
internal BewitBuilder(IServiceCollection services)
@@ -65,4 +67,11 @@ public BewitBuilder ConfigureTokenExtraction(
6567

6668
return this;
6769
}
70+
71+
public BewitBuilder BindTokenExtractionConfiguration(string sectionPath)
72+
{
73+
TokenExtractionConfigSection = sectionPath;
74+
75+
return this;
76+
}
6877
}

src/Core/BewitServiceCollectionExtensions.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,24 @@ public static IServiceCollection AddBewit(
1515
services.TryAddSingleton<IVariablesProvider, VariablesProvider>();
1616
services.AddHttpContextAccessor();
1717

18-
if (builder.TokenExtractionAction is not null)
18+
var optionsBuilder = services
19+
.AddOptions<BewitTokenExtractionOptions>();
20+
21+
if (builder.TokenExtractionConfigSection is not null)
1922
{
20-
services.Configure(builder.TokenExtractionAction);
23+
optionsBuilder.BindConfiguration(
24+
builder.TokenExtractionConfigSection);
2125
}
22-
else
26+
27+
if (builder.TokenExtractionAction is not null)
2328
{
24-
services.TryAddSingleton(
25-
Microsoft.Extensions.Options.Options.Create(
26-
new BewitTokenExtractionOptions()));
29+
optionsBuilder.Configure(builder.TokenExtractionAction);
2730
}
2831

32+
optionsBuilder
33+
.ValidateDataAnnotations()
34+
.ValidateOnStart();
35+
2936
foreach (Action<IServiceCollection> registration in builder.PayloadRegistrations)
3037
{
3138
registration(services);
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
using System.ComponentModel.DataAnnotations;
2+
13
namespace Bewit;
24

35
public sealed class BewitTokenExtractionOptions
46
{
7+
[Required]
58
public string HeaderName { get; set; } = "bewitToken";
69

10+
[Required]
711
public string QueryParamName { get; set; } = "bewit";
812

9-
internal string ContextKey { get; set; } = "bewitToken";
13+
internal string ContextKey => HeaderName;
1014
}

src/Extensions.HotChocolate/BewitHotChocolateServiceCollectionExtensions.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,4 @@ public static IApplicationBuilder UseBewitTokenExtraction(
1010
{
1111
return app.UseMiddleware<BewitTokenExtractionMiddleware>();
1212
}
13-
14-
[Obsolete("Use UseBewitTokenExtraction instead.")]
15-
public static IApplicationBuilder UseBewitTokenHeaderExtraction(
16-
this IApplicationBuilder app)
17-
{
18-
return app.UseBewitTokenExtraction();
19-
}
2013
}

src/Extensions.HotChocolate/BewitTokenConstants.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

test/Extensions.HotChocolate.Tests/BewitTokenExtractionMiddlewareTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public async Task InvokeAsync_WithCustomHeaderName_ShouldReadFromCustomHeader()
113113

114114
await middleware.InvokeAsync(context);
115115

116-
context.Items["bewitToken"].Should().Be("custom-header-token");
116+
context.Items["X-My-Token"].Should().Be("custom-header-token");
117117
}
118118

119119
[Fact]

0 commit comments

Comments
 (0)