Skip to content

Commit 278bb18

Browse files
authored
Merge pull request #166 from marcominerva/copilot/add-roles-parameter-authentication
Add Roles support to ApiKey and Basic Authentication
2 parents 0cd22eb + 1dbe514 commit 278bb18

13 files changed

Lines changed: 148 additions & 24 deletions

File tree

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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):

samples/MinimalApis/ApiKeySample/Program.cs

Lines changed: 12 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

@@ -55,16 +56,22 @@
5556
app.UseAuthentication();
5657
app.UseAuthorization();
5758

58-
app.MapGet("api/me", (ClaimsPrincipal user) =>
59+
app.MapGet("api/me", (ClaimsPrincipal user, IOptions<ApiKeySettings> options) =>
5960
{
60-
return TypedResults.Ok(new User(user.Identity!.Name));
61+
var roles = user.FindAll(options.Value.RoleClaimType).Select(c => c.Value);
62+
return TypedResults.Ok(new User(user.Identity!.Name, roles));
6163
})
62-
.RequireAuthorization()
63-
.WithOpenApi();
64+
.RequireAuthorization();
65+
66+
app.MapGet("api/admin", () => "Administrator access granted")
67+
.RequireAuthorization(policy => policy.RequireRole("Administrator"));
68+
69+
app.MapGet("api/user", () => "User access granted")
70+
.RequireAuthorization(policy => policy.RequireRole("User"));
6471

6572
app.Run();
6673

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

6976
public class CustomApiKeyValidator : IApiKeyValidator
7077
{

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.

samples/MinimalApis/BasicAuthenticationSample/Program.cs

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

@@ -55,16 +56,22 @@
5556
app.UseAuthentication();
5657
app.UseAuthorization();
5758

58-
app.MapGet("api/me", (ClaimsPrincipal user) =>
59+
app.MapGet("api/me", (ClaimsPrincipal user, IOptions<BasicAuthenticationSettings> options) =>
5960
{
60-
return TypedResults.Ok(new User(user.Identity!.Name));
61+
var roles = user.FindAll(options.Value.RoleClaimType).Select(c => c.Value);
62+
return TypedResults.Ok(new User(user.Identity!.Name, roles));
6163
})
62-
.RequireAuthorization()
63-
.WithOpenApi();
64+
.RequireAuthorization();
65+
66+
app.MapGet("api/admin", () => "Administrator access granted")
67+
.RequireAuthorization(policy => policy.RequireRole("Administrator"));
68+
69+
app.MapGet("api/user", () => "User access granted")
70+
.RequireAuthorization(policy => policy.RequireRole("User"));
6471

6572
app.Run();
6673

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

6976
public class CustomBasicAuthenticationValidator : IBasicAuthenticationValidator
7077
{

samples/MinimalApis/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.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
namespace SimpleAuthentication.ApiKey;
1+
namespace SimpleAuthentication.ApiKey;
22

33
/// <summary>
44
/// Store API Keys for API Key Authentication
55
/// </summary>
66
/// <param name="Value">The API key value</param>
77
/// <param name="UserName">The user name associated with the current key</param>
8-
public record class ApiKey(string Value, string UserName);
8+
/// <param name="Roles">The optional list of roles to assign to the user</param>
9+
public record class ApiKey(string Value, string UserName, IEnumerable<string>? Roles = null);

src/SimpleAuthentication.Abstractions/ApiKey/ApiKeySettings.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ public class ApiKeySettings : AuthenticationSchemeOptions
4141
/// <seealso cref="ApiKeyValue"/>
4242
public string? UserName { get; set; }
4343

44+
/// <summary>
45+
/// Gets or sets the optional list of roles to assign to the user when using <see cref="ApiKeyValue"/> and <see cref="UserName"/>.
46+
/// </summary>
47+
/// <seealso cref="ApiKeyValue"/>
48+
/// <seealso cref="UserName"/>
49+
public IEnumerable<string>? Roles { get; set; }
50+
4451
/// <summary>
4552
/// The collection of valid API keys.
4653
/// </summary>
@@ -72,7 +79,7 @@ internal IEnumerable<ApiKey> GetAllApiKeys()
7279
if (!string.IsNullOrWhiteSpace(ApiKeyValue) && !string.IsNullOrWhiteSpace(UserName))
7380
{
7481
// If necessary, add the API Key from the base properties.
75-
apiKeys.Add(new(ApiKeyValue, UserName));
82+
apiKeys.Add(new(ApiKeyValue, UserName, Roles));
7683
}
7784

7885
return apiKeys;

src/SimpleAuthentication.Abstractions/BasicAuthentication/BasicAuthenticationSettings.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ public class BasicAuthenticationSettings : AuthenticationSchemeOptions
3030
/// <seealso cref="IBasicAuthenticationValidator"/>
3131
public string? Password { get; set; }
3232

33+
/// <summary>
34+
/// Gets or sets the optional list of roles to assign to the user when using <see cref="UserName"/> and <see cref="Password"/>.
35+
/// </summary>
36+
/// <seealso cref="UserName"/>
37+
/// <seealso cref="Password"/>
38+
public IEnumerable<string>? Roles { get; set; }
39+
3340
/// <summary>
3441
/// The collection of authorization credentials.
3542
/// </summary>
@@ -61,7 +68,7 @@ internal IEnumerable<Credential> GetAllCredentials()
6168
if (!string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password))
6269
{
6370
// If necessary, add the Credentials from the base properties.
64-
credentials.Add(new(UserName, Password));
71+
credentials.Add(new(UserName, Password, Roles));
6572
}
6673

6774
return credentials;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
namespace SimpleAuthentication.BasicAuthentication;
1+
namespace SimpleAuthentication.BasicAuthentication;
22

33
/// <summary>
44
/// Store credentials used for Basic Authentication.
55
/// </summary>
66
/// <param name="UserName">The user name</param>
77
/// <param name="Password">The password</param>
8-
public record class Credential(string UserName, string Password);
8+
/// <param name="Roles">The optional list of roles to assign to the user</param>
9+
public record class Credential(string UserName, string Password, IEnumerable<string>? Roles = null);

src/SimpleAuthentication.Swashbuckle/SimpleAuthentication.Swashbuckle.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
</ItemGroup>
3333

3434
<ItemGroup>
35-
<PackageReference Include="SimpleAuthenticationTools.Abstractions" Version="3.0.13" />
35+
<ProjectReference Include="..\SimpleAuthentication.Abstractions\SimpleAuthentication.Abstractions.csproj" />
3636
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.6" />
3737
</ItemGroup>
3838

0 commit comments

Comments
 (0)