Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d4cd6d7
Initial plan
Copilot Oct 8, 2025
6442cd6
Add Roles support to ApiKey and Basic Authentication
Copilot Oct 8, 2025
1f7daa2
Update sample applications to test roles feature and fix project refe…
Copilot Oct 8, 2025
92de02f
Address PR feedback: add Roles to single credentials, rename Admin to…
Copilot Oct 10, 2025
113053b
Update README to document Roles support for single credentials
Copilot Oct 10, 2025
57fc9b8
Move ApiKey and Credential classes to separate files
Copilot Oct 10, 2025
a356735
Change Roles type from string[] to IEnumerable<string>
Copilot Oct 10, 2025
01b8ee3
Make Roles property non-nullable with empty array default in settings…
Copilot Oct 10, 2025
2128ebc
Remove WithOpenApi extension method calls from sample endpoints
Copilot Oct 10, 2025
66784b8
Initial plan
Copilot Oct 10, 2025
3e14dd7
Remove EnableJwtBearerService setting and always register IJwtBearerS…
Copilot Oct 10, 2025
1a5e0ae
Merge branch 'develop' to resolve conflicts
Copilot Oct 10, 2025
e9accf0
Update package references to version 3.0.13
marcominerva Oct 15, 2025
c5e8923
Merge branch 'develop' to resolve conflicts with latest changes
Copilot Oct 15, 2025
afc88fb
Merge branch 'develop' into copilot/remove-jwt-bearer-setting
Copilot Oct 15, 2025
1cfb988
Merge commit
marcominerva Oct 15, 2025
0cd22eb
Merge pull request #168 from marcominerva/copilot/remove-jwt-bearer-s…
marcominerva Oct 15, 2025
4fe25c2
Merge branch 'develop' into copilot/add-roles-parameter-authentication
Copilot Oct 15, 2025
1dbe514
Merge develop branch and fix roles configuration binding
Copilot Oct 15, 2025
278bb18
Merge pull request #166 from marcominerva/copilot/add-roles-parameter…
marcominerva Oct 15, 2025
6fcbac8
Enhance authentication setup and endpoint clarity
marcominerva Oct 15, 2025
c80526e
Add role-based authorization to MeController
marcominerva Oct 15, 2025
8943e6d
Enhance parameter validation logic
marcominerva Oct 16, 2025
9bcf25f
Merge branch 'master' into develop
marcominerva Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ builder.Services.AddOpenApi(options =>

**Creating a JWT Bearer**

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:
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:

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

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

**Assigning roles to API Keys and Basic Authentication credentials**

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.

For single credentials, you can specify roles directly:

```json
"Authentication": {
"ApiKey": {
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
"UserName": "ApiUser",
"Roles": ["Administrator"]
},
"Basic": {
"UserName": "marco",
"Password": "P@$$w0rd",
"Roles": ["Administrator"]
}
}
```

For multiple credentials, you can specify roles for each credential:

```json
"Authentication": {
"ApiKey": {
"ApiKeys": [
{
"Value": "key-1",
"UserName": "UserName1",
"Roles": ["Administrator", "User"]
},
{
"Value": "key-2",
"UserName": "UserName2",
"Roles": ["User"]
}
]
},
"Basic": {
"Credentials": [
{
"UserName": "UserName1",
"Password": "Password1",
"Roles": ["Manager", "User"]
},
{
"UserName": "UserName2",
"Password": "Password2",
"Roles": ["User"]
}
]
}
}
```

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:

```csharp
[Authorize(Roles = "Administrator")]
public IActionResult AdminEndpoint()
{
return Ok("Administrator access granted");
}

// Or with minimal APIs
app.MapGet("/admin", () => "Administrator access granted")
.RequireAuthorization(policy => policy.RequireRole("Administrator"));
```

**Custom Authentication logic for API Keys and Basic Authentication**

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):
Expand Down Expand Up @@ -316,4 +386,4 @@ app.MapGet("api/me", (ClaimsPrincipal user) =>

## Contribute

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.
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.
27 changes: 24 additions & 3 deletions samples/Controllers/ApiKeySample/Controllers/MeController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Net.Mime;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using SimpleAuthentication.ApiKey;

namespace ApiKeySample.Controllers;

Expand All @@ -13,8 +15,27 @@ public class MeController : ControllerBase
[HttpGet]
[ProducesResponseType<User>(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public ActionResult<User> Get()
=> new User(User.Identity!.Name);
public ActionResult<User> Get(IOptions<ApiKeySettings> apiKeySettingsOptions)
{
// Get roles using the configured role claim type from options (default is ClaimTypes.Role)
var roles = User.FindAll(apiKeySettingsOptions.Value.RoleClaimType).Select(c => c.Value);

return new User(User.Identity!.Name, roles);
}

[Authorize(Roles = "Administrator")]
[HttpGet("administrator")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[EndpointDescription("This endpoint requires the user to have the 'Administrator' role")]
public IActionResult AdministratorOnly()
=> NoContent();

[Authorize(Roles = "User")]
[HttpGet("user")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[EndpointDescription("This endpoint requires the user to have the 'User' role")]
public IActionResult UserOnly()
=> NoContent();
}

public record class User(string? UserName);
public record class User(string? UserName, IEnumerable<string> Roles);
1 change: 1 addition & 0 deletions samples/Controllers/ApiKeySample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// .Build())
// .AddPolicy("ApiKey", builder => builder.AddAuthenticationSchemes(ApiKeyDefaults.AuthenticationScheme).RequireAuthenticatedUser());

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

// Uncomment the following line if you have multiple authentication schemes and
Expand Down
9 changes: 7 additions & 2 deletions samples/Controllers/ApiKeySample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@
// You can specify either HeaderName, QueryStringKey or both
"HeaderName": "x-api-key",
"QueryStringKey": "code",
//"NameClaimType": "user_name", // Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// You can set a fixed API Key for authentication. If you have a single value, you can just use the plain property:
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
"UserName": "ApiUser", // Required if ApiKeyValue is used
"Roles": [ "Administrator" ],
// Otherwise, you can create an array of ApiKeys:
"ApiKeys": [
{
"Value": "ArAilHVOoL3upX78Cohq",
"UserName": "alice"
"UserName": "alice",
"Roles": [ "Administrator", "User" ]
},
{
"Value": "DiUU5EqImTYkxPDAxBVS",
"UserName": "bob"
"UserName": "bob",
"Roles": [ "User" ]
}
]
// You can also combine both declarations.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Net.Mime;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using SimpleAuthentication.BasicAuthentication;

namespace BasicAuthenticationSample.Controllers;

Expand All @@ -13,8 +15,27 @@ public class MeController : ControllerBase
[HttpGet]
[ProducesResponseType<User>(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public ActionResult<User> Get()
=> new User(User.Identity!.Name);
public ActionResult<User> Get(IOptions<BasicAuthenticationSettings> basicAuthenticationSettingsOptions)
{
// Get roles using the configured role claim type from options (default is ClaimTypes.Role)
var roles = User.FindAll(basicAuthenticationSettingsOptions.Value.RoleClaimType).Select(c => c.Value);

return new User(User.Identity!.Name, roles);
}

[Authorize(Roles = "Administrator")]
[HttpGet("administrator")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[EndpointDescription("This endpoint requires the user to have the 'Administrator' role")]
public IActionResult AdministratorOnly()
=> NoContent();

[Authorize(Roles = "User")]
[HttpGet("user")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[EndpointDescription("This endpoint requires the user to have the 'User' role")]
public IActionResult UserOnly()
=> NoContent();
}

public record class User(string? UserName);
public record class User(string? UserName, IEnumerable<string> Roles);
1 change: 1 addition & 0 deletions samples/Controllers/BasicAuthenticationSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// .Build())
// .AddPolicy("Basic", builder => builder.AddAuthenticationSchemes(BasicAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser());

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

// Uncomment the following line if you have multiple authentication schemes and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
"UserName": "marco",
"Password": "P@$$w0rd",
"Roles": [ "Administrator" ],
// Otherwise, you can create an array of Credentials:
"Credentials": [
{
"UserName": "alice",
"Password": "Password1"
"Password": "Password1",
"Roles": [ "Administrator", "User" ]
},
{
"UserName": "bob",
"Password": "Password2"
"Password": "Password2",
"Roles": [ "User" ]
}
]
// You can also combine both declarations.
Expand Down
3 changes: 1 addition & 2 deletions samples/Controllers/JwtBearerSample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"Issuers": [ "issuer" ], // Optional
"Audiences": [ "audience" ], // Optional
"ExpirationTime": "01:00:00", // Default: No expiration
"ClockSkew": "00:02:00", // Default: 5 minutes
"EnableJwtBearerService": true // Default: true
"ClockSkew": "00:02:00" // Default: 5 minutes
}
},
"Logging": {
Expand Down
20 changes: 15 additions & 5 deletions samples/MinimalApis/ApiKeySample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Claims;
using ApiKeySample.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using SimpleAuthentication;
using SimpleAuthentication.ApiKey;

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

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

// Uncomment the following line if you have multiple authentication schemes and
Expand Down Expand Up @@ -55,16 +57,24 @@
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("api/me", (ClaimsPrincipal user) =>
app.MapGet("api/me", (ClaimsPrincipal user, IOptions<ApiKeySettings> options) =>
{
return TypedResults.Ok(new User(user.Identity!.Name));
var roles = user.FindAll(options.Value.RoleClaimType).Select(c => c.Value);
return TypedResults.Ok(new User(user.Identity!.Name, roles));
})
.RequireAuthorization()
.WithOpenApi();
.RequireAuthorization();

app.MapGet("api/administrator", () => TypedResults.NoContent())
.WithDescription("This endpoint requires the user to have the 'Administrator' role")
.RequireAuthorization(policy => policy.RequireRole("Administrator"));

app.MapGet("api/user", () => TypedResults.NoContent())
.WithDescription("This endpoint requires the user to have the 'User' role")
.RequireAuthorization(policy => policy.RequireRole("User"));

app.Run();

public record class User(string? UserName);
public record class User(string? UserName, IEnumerable<string> Roles);

public class CustomApiKeyValidator : IApiKeyValidator
{
Expand Down
7 changes: 5 additions & 2 deletions samples/MinimalApis/ApiKeySample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@
// You can set a fixed API Key for authentication. If you have a single value, you can just use the plain property:
"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0",
"UserName": "ApiUser", // Required if ApiKeyValue is used
"Roles": [ "Administrator" ],
// Otherwise, you can create an array of ApiKeys:
"ApiKeys": [
{
"Value": "ArAilHVOoL3upX78Cohq",
"UserName": "alice"
"UserName": "alice",
"Roles": [ "Administrator", "User" ]
},
{
"Value": "DiUU5EqImTYkxPDAxBVS",
"UserName": "bob"
"UserName": "bob",
"Roles": [ "User" ]
}
]
// You can also combine both declarations.
Expand Down
20 changes: 15 additions & 5 deletions samples/MinimalApis/BasicAuthenticationSample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Claims;
using BasicAuthenticationSample.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using SimpleAuthentication;
using SimpleAuthentication.BasicAuthentication;

Expand All @@ -24,6 +25,7 @@
// .Build())
// .AddPolicy("Basic", builder => builder.AddAuthenticationSchemes(BasicAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser());

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

// Uncomment the following line if you have multiple authentication schemes and
Expand Down Expand Up @@ -55,16 +57,24 @@
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("api/me", (ClaimsPrincipal user) =>
app.MapGet("api/me", (ClaimsPrincipal user, IOptions<BasicAuthenticationSettings> options) =>
{
return TypedResults.Ok(new User(user.Identity!.Name));
var roles = user.FindAll(options.Value.RoleClaimType).Select(c => c.Value);
return TypedResults.Ok(new User(user.Identity!.Name, roles));
})
.RequireAuthorization()
.WithOpenApi();
.RequireAuthorization();

app.MapGet("api/administrator", () => TypedResults.NoContent())
.WithDescription("This endpoint requires the user to have the 'Administrator' role")
.RequireAuthorization(policy => policy.RequireRole("Administrator"));

app.MapGet("api/user", () => TypedResults.NoContent())
.WithDescription("This endpoint requires the user to have the 'User' role")
.RequireAuthorization(policy => policy.RequireRole("User"));

app.Run();

public record class User(string? UserName);
public record class User(string? UserName, IEnumerable<string> Roles);

public class CustomBasicAuthenticationValidator : IBasicAuthenticationValidator
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
"UserName": "marco",
"Password": "P@$$w0rd",
"Roles": [ "Administrator" ],
// Otherwise, you can create an array of Credentials:
"Credentials": [
{
"UserName": "alice",
"Password": "Password1"
"Password": "Password1",
"Roles": [ "Administrator", "User" ]
},
{
"UserName": "bob",
"Password": "Password2"
"Password": "Password2",
"Roles": [ "User" ]
}
]
// You can also combine both declarations.
Expand Down
3 changes: 1 addition & 2 deletions samples/MinimalApis/JwtBearerSample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"Issuers": [ "issuer" ], // Optional
"Audiences": [ "audience" ], // Optional
"ExpirationTime": "01:00:00", // Default: No expiration
"ClockSkew": "00:02:00", // Default: 5 minutes
"EnableJwtBearerService": true // Default: true
"ClockSkew": "00:02:00" // Default: 5 minutes
}
},
"Logging": {
Expand Down
Loading