Skip to content

Commit f2b89d1

Browse files
authored
Merge pull request #170 from marcominerva/develop
Allow specifying user roles for ApiKey and Basic Authentication
2 parents 686c144 + 9bcf25f commit f2b89d1

File tree

31 files changed

+298
-82
lines changed

31 files changed

+298
-82
lines changed

README.md

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ builder.Services.AddOpenApi(options =>
138138

139139
**Creating a JWT Bearer**
140140

141-
When using JWT Bearer authentication, you can set the _EnableJwtBearerService_ setting to _true_ to automatically register an implementation of the [IJwtBearerService](https://github.com/marcominerva/SimpleAuthentication/blob/master/src/SimpleAuthentication.Abstractions/JwtBearer/IJwtBearerService.cs) interface to create a valid JWT Bearer, according to the setting you have specified in the _appsettings.json_ file:
141+
When using JWT Bearer authentication, an implementation of the [IJwtBearerService](https://github.com/marcominerva/SimpleAuthentication/blob/master/src/SimpleAuthentication.Abstractions/JwtBearer/IJwtBearerService.cs) interface is automatically registered to create a valid JWT Bearer, according to the settings you have specified in the _appsettings.json_ file:
142142

143143
```csharp
144144
app.MapPost("api/auth/login", async (LoginRequest loginRequest, IJwtBearerService jwtBearerService) =>
@@ -198,6 +198,76 @@ When using API Key or Basic Authentication, you can specify multiple fixed value
198198

199199
With this configuration, authentication will succeed if any of these credentials are provided.
200200

201+
**Assigning roles to API Keys and Basic Authentication credentials**
202+
203+
You can optionally specify roles for each API Key or Basic Authentication credential. When authentication succeeds, the specified roles will be automatically added as role claims to the user's identity.
204+
205+
For single credentials, you can specify roles directly:
206+
207+
```json
208+
"Authentication": {
209+
"ApiKey": {
210+
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
211+
"UserName": "ApiUser",
212+
"Roles": ["Administrator"]
213+
},
214+
"Basic": {
215+
"UserName": "marco",
216+
"Password": "P@$$w0rd",
217+
"Roles": ["Administrator"]
218+
}
219+
}
220+
```
221+
222+
For multiple credentials, you can specify roles for each credential:
223+
224+
```json
225+
"Authentication": {
226+
"ApiKey": {
227+
"ApiKeys": [
228+
{
229+
"Value": "key-1",
230+
"UserName": "UserName1",
231+
"Roles": ["Administrator", "User"]
232+
},
233+
{
234+
"Value": "key-2",
235+
"UserName": "UserName2",
236+
"Roles": ["User"]
237+
}
238+
]
239+
},
240+
"Basic": {
241+
"Credentials": [
242+
{
243+
"UserName": "UserName1",
244+
"Password": "Password1",
245+
"Roles": ["Manager", "User"]
246+
},
247+
{
248+
"UserName": "UserName2",
249+
"Password": "Password2",
250+
"Roles": ["User"]
251+
}
252+
]
253+
}
254+
}
255+
```
256+
257+
The `Roles` parameter is optional. If omitted, no role claims will be added to the user's identity. You can then use the standard ASP.NET Core authorization features to check for roles:
258+
259+
```csharp
260+
[Authorize(Roles = "Administrator")]
261+
public IActionResult AdminEndpoint()
262+
{
263+
return Ok("Administrator access granted");
264+
}
265+
266+
// Or with minimal APIs
267+
app.MapGet("/admin", () => "Administrator access granted")
268+
.RequireAuthorization(policy => policy.RequireRole("Administrator"));
269+
```
270+
201271
**Custom Authentication logic for API Keys and Basic Authentication**
202272

203273
If you need to implement custom authentication logic, for example validating credentials with dynamic values and adding claims to identity, you can omit all the credentials in the _appsettings.json_ file and then provide an implementation of [IApiKeyValidator.cs](https://github.com/marcominerva/SimpleAuthentication/blob/master/src/SimpleAuthentication.Abstractions/ApiKey/IApiKeyValidator.cs) or [IBasicAuthenticationValidator.cs](https://github.com/marcominerva/SimpleAuthentication/blob/master/src/SimpleAuthentication.Abstractions/BasicAuthentication/IBasicAuthenticationValidator.cs):
@@ -316,4 +386,4 @@ app.MapGet("api/me", (ClaimsPrincipal user) =>
316386

317387
## Contribute
318388

319-
The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests in the repository, and we'll address them as we can.
389+
The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests in the repository, and we'll address them as we can.

samples/Controllers/ApiKeySample/Controllers/MeController.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Net.Mime;
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.Extensions.Options;
5+
using SimpleAuthentication.ApiKey;
46

57
namespace ApiKeySample.Controllers;
68

@@ -13,8 +15,27 @@ public class MeController : ControllerBase
1315
[HttpGet]
1416
[ProducesResponseType<User>(StatusCodes.Status200OK)]
1517
[ProducesDefaultResponseType]
16-
public ActionResult<User> Get()
17-
=> new User(User.Identity!.Name);
18+
public ActionResult<User> Get(IOptions<ApiKeySettings> apiKeySettingsOptions)
19+
{
20+
// Get roles using the configured role claim type from options (default is ClaimTypes.Role)
21+
var roles = User.FindAll(apiKeySettingsOptions.Value.RoleClaimType).Select(c => c.Value);
22+
23+
return new User(User.Identity!.Name, roles);
24+
}
25+
26+
[Authorize(Roles = "Administrator")]
27+
[HttpGet("administrator")]
28+
[ProducesResponseType(StatusCodes.Status204NoContent)]
29+
[EndpointDescription("This endpoint requires the user to have the 'Administrator' role")]
30+
public IActionResult AdministratorOnly()
31+
=> NoContent();
32+
33+
[Authorize(Roles = "User")]
34+
[HttpGet("user")]
35+
[ProducesResponseType(StatusCodes.Status204NoContent)]
36+
[EndpointDescription("This endpoint requires the user to have the 'User' role")]
37+
public IActionResult UserOnly()
38+
=> NoContent();
1839
}
1940

20-
public record class User(string? UserName);
41+
public record class User(string? UserName, IEnumerable<string> Roles);

samples/Controllers/ApiKeySample/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
// .Build())
2525
// .AddPolicy("ApiKey", builder => builder.AddAuthenticationSchemes(ApiKeyDefaults.AuthenticationScheme).RequireAuthenticatedUser());
2626

27+
// This service is used when there isn't a fixed API Key value in the configuration.
2728
builder.Services.AddTransient<IApiKeyValidator, CustomApiKeyValidator>();
2829

2930
// Uncomment the following line if you have multiple authentication schemes and

samples/Controllers/ApiKeySample/appsettings.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@
66
// You can specify either HeaderName, QueryStringKey or both
77
"HeaderName": "x-api-key",
88
"QueryStringKey": "code",
9+
//"NameClaimType": "user_name", // Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
10+
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
911
// You can set a fixed API Key for authentication. If you have a single value, you can just use the plain property:
1012
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
1113
"UserName": "ApiUser", // Required if ApiKeyValue is used
14+
"Roles": [ "Administrator" ],
1215
// Otherwise, you can create an array of ApiKeys:
1316
"ApiKeys": [
1417
{
1518
"Value": "ArAilHVOoL3upX78Cohq",
16-
"UserName": "alice"
19+
"UserName": "alice",
20+
"Roles": [ "Administrator", "User" ]
1721
},
1822
{
1923
"Value": "DiUU5EqImTYkxPDAxBVS",
20-
"UserName": "bob"
24+
"UserName": "bob",
25+
"Roles": [ "User" ]
2126
}
2227
]
2328
// You can also combine both declarations.

samples/Controllers/BasicAuthenticationSample/Controllers/MeController.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Net.Mime;
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.Extensions.Options;
5+
using SimpleAuthentication.BasicAuthentication;
46

57
namespace BasicAuthenticationSample.Controllers;
68

@@ -13,8 +15,27 @@ public class MeController : ControllerBase
1315
[HttpGet]
1416
[ProducesResponseType<User>(StatusCodes.Status200OK)]
1517
[ProducesDefaultResponseType]
16-
public ActionResult<User> Get()
17-
=> new User(User.Identity!.Name);
18+
public ActionResult<User> Get(IOptions<BasicAuthenticationSettings> basicAuthenticationSettingsOptions)
19+
{
20+
// Get roles using the configured role claim type from options (default is ClaimTypes.Role)
21+
var roles = User.FindAll(basicAuthenticationSettingsOptions.Value.RoleClaimType).Select(c => c.Value);
22+
23+
return new User(User.Identity!.Name, roles);
24+
}
25+
26+
[Authorize(Roles = "Administrator")]
27+
[HttpGet("administrator")]
28+
[ProducesResponseType(StatusCodes.Status204NoContent)]
29+
[EndpointDescription("This endpoint requires the user to have the 'Administrator' role")]
30+
public IActionResult AdministratorOnly()
31+
=> NoContent();
32+
33+
[Authorize(Roles = "User")]
34+
[HttpGet("user")]
35+
[ProducesResponseType(StatusCodes.Status204NoContent)]
36+
[EndpointDescription("This endpoint requires the user to have the 'User' role")]
37+
public IActionResult UserOnly()
38+
=> NoContent();
1839
}
1940

20-
public record class User(string? UserName);
41+
public record class User(string? UserName, IEnumerable<string> Roles);

samples/Controllers/BasicAuthenticationSample/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// .Build())
2626
// .AddPolicy("Basic", builder => builder.AddAuthenticationSchemes(BasicAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser());
2727

28+
// This service is used when there isn't a fixed user/password value in the configuration.
2829
builder.Services.AddTransient<IBasicAuthenticationValidator, CustomBasicAuthenticationValidator>();
2930

3031
// Uncomment the following line if you have multiple authentication schemes and

samples/Controllers/BasicAuthenticationSample/appsettings.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
99
"UserName": "marco",
1010
"Password": "P@$$w0rd",
11+
"Roles": [ "Administrator" ],
1112
// Otherwise, you can create an array of Credentials:
1213
"Credentials": [
1314
{
1415
"UserName": "alice",
15-
"Password": "Password1"
16+
"Password": "Password1",
17+
"Roles": [ "Administrator", "User" ]
1618
},
1719
{
1820
"UserName": "bob",
19-
"Password": "Password2"
21+
"Password": "Password2",
22+
"Roles": [ "User" ]
2023
}
2124
]
2225
// You can also combine both declarations.

samples/Controllers/JwtBearerSample/appsettings.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"Issuers": [ "issuer" ], // Optional
1111
"Audiences": [ "audience" ], // Optional
1212
"ExpirationTime": "01:00:00", // Default: No expiration
13-
"ClockSkew": "00:02:00", // Default: 5 minutes
14-
"EnableJwtBearerService": true // Default: true
13+
"ClockSkew": "00:02:00" // Default: 5 minutes
1514
}
1615
},
1716
"Logging": {

samples/MinimalApis/ApiKeySample/Program.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Security.Claims;
22
using ApiKeySample.Authentication;
33
using Microsoft.AspNetCore.Authentication;
4+
using Microsoft.Extensions.Options;
45
using SimpleAuthentication;
56
using SimpleAuthentication.ApiKey;
67

@@ -24,6 +25,7 @@
2425
// .Build())
2526
// .AddPolicy("ApiKey", builder => builder.AddAuthenticationSchemes(ApiKeyDefaults.AuthenticationScheme).RequireAuthenticatedUser());
2627

28+
// This service is used when there isn't a fixed API Key value in the configuration.
2729
builder.Services.AddTransient<IApiKeyValidator, CustomApiKeyValidator>();
2830

2931
// Uncomment the following line if you have multiple authentication schemes and
@@ -55,16 +57,24 @@
5557
app.UseAuthentication();
5658
app.UseAuthorization();
5759

58-
app.MapGet("api/me", (ClaimsPrincipal user) =>
60+
app.MapGet("api/me", (ClaimsPrincipal user, IOptions<ApiKeySettings> options) =>
5961
{
60-
return TypedResults.Ok(new User(user.Identity!.Name));
62+
var roles = user.FindAll(options.Value.RoleClaimType).Select(c => c.Value);
63+
return TypedResults.Ok(new User(user.Identity!.Name, roles));
6164
})
62-
.RequireAuthorization()
63-
.WithOpenApi();
65+
.RequireAuthorization();
66+
67+
app.MapGet("api/administrator", () => TypedResults.NoContent())
68+
.WithDescription("This endpoint requires the user to have the 'Administrator' role")
69+
.RequireAuthorization(policy => policy.RequireRole("Administrator"));
70+
71+
app.MapGet("api/user", () => TypedResults.NoContent())
72+
.WithDescription("This endpoint requires the user to have the 'User' role")
73+
.RequireAuthorization(policy => policy.RequireRole("User"));
6474

6575
app.Run();
6676

67-
public record class User(string? UserName);
77+
public record class User(string? UserName, IEnumerable<string> Roles);
6878

6979
public class CustomApiKeyValidator : IApiKeyValidator
7080
{

samples/MinimalApis/ApiKeySample/appsettings.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111
// You can set a fixed API Key for authentication. If you have a single value, you can just use the plain property:
1212
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
1313
"UserName": "ApiUser", // Required if ApiKeyValue is used
14+
"Roles": [ "Administrator" ],
1415
// Otherwise, you can create an array of ApiKeys:
1516
"ApiKeys": [
1617
{
1718
"Value": "ArAilHVOoL3upX78Cohq",
18-
"UserName": "alice"
19+
"UserName": "alice",
20+
"Roles": [ "Administrator", "User" ]
1921
},
2022
{
2123
"Value": "DiUU5EqImTYkxPDAxBVS",
22-
"UserName": "bob"
24+
"UserName": "bob",
25+
"Roles": [ "User" ]
2326
}
2427
]
2528
// You can also combine both declarations.

0 commit comments

Comments
 (0)