Skip to content

Commit

Permalink
Merge pull request #118 from marcominerva/develop
Browse files Browse the repository at this point in the history
Replace JwtSecurityToken with JsonWebToken and add async processing
  • Loading branch information
marcominerva authored Sep 9, 2024
2 parents 484ad0e + bfe9fcc commit f9b2d06
Show file tree
Hide file tree
Showing 18 changed files with 241 additions and 170 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Authentication can be totally configured adding an _Authentication_ section in t
"JwtBearer": {
"SchemeName": "Bearer" // Default: Bearer
//"NameClaimType": "user_name", // Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
//"RoleClaimType": "role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
"SecurityKey": "supersecretsecuritykey42!", // Required
"Algorithm": "HS256", // Default: HS256
"Issuers": [ "issuer" ], // Optional
Expand All @@ -39,10 +39,12 @@ Authentication can be totally configured adding an _Authentication_ section in t
"EnableJwtBearerService": true // Default: true
},
"ApiKey": {
"SchemeName": "MyApiKeyScheme", // Default: ApiKey
"SchemeName": "ApiKey", // Default: ApiKey
// 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
// Uncomment this line if you want to validate the API Key against a fixed value.
// Otherwise, you need to register an IApiKeyValidator implementation that will be used
// to validate the API Key.
Expand All @@ -51,6 +53,8 @@ Authentication can be totally configured adding an _Authentication_ section in t
},
"Basic": {
"SchemeName": "Basic", // Default: Basic
//"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
// Uncomment the following lines if you want to validate user name and password
// against fixed values.
// Otherwise, you need to register an IBasicAuthenticationValidator implementation
Expand Down Expand Up @@ -87,7 +91,7 @@ The _DefaultScheme_ attribute is used to specify what kind of authentication mus
var app = builder.Build();

//...
// The following middlewares aren't strictly necessary in .NET 7.0, because they are automatically
// The following middlewares aren't strictly necessary in .NET 7.0 or higher, because they are automatically
// added when detecting that the corresponding services have been registered. However, you may
// need to call them explicitly if the default middlewares configuration is not correct for your
// app, for example when you need to use CORS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"Basic": {
"SchemeName": "Basic", // Default: Basic
// You can set a fixed user name and password for authentication. If you have a single credential, you can just use the plain properties:
//"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
"UserName": "marco",
"Password": "P@$$w0rd",
// Otherwise, you can create an array of Credentials:
Expand Down
18 changes: 10 additions & 8 deletions samples/Controllers/JwtBearerSample/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class AuthController(IJwtBearerService jwtBearerService) : ControllerBase
[ProducesResponseType<LoginResponse>(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
[SwaggerOperation(description: "Insert permissions in the scope property (for example: 'profile people:admin')")]
public ActionResult<LoginResponse> Login(LoginRequest loginRequest, DateTime? expiration = null)
public async Task<ActionResult<LoginResponse>> Login(LoginRequest loginRequest, DateTime? expiration = null)
{
// Check for login rights...

Expand All @@ -26,30 +26,32 @@ public ActionResult<LoginResponse> Login(LoginRequest loginRequest, DateTime? ex
claims.Add(new("scp", loginRequest.Scopes));
}

var token = jwtBearerService.CreateToken(loginRequest.UserName, claims, absoluteExpiration: expiration);
var token = await jwtBearerService.CreateTokenAsync(loginRequest.UserName, claims, absoluteExpiration: expiration);
return new LoginResponse(token);
}

[HttpPost("validate")]
[ProducesResponseType<User>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesDefaultResponseType]
public ActionResult<User> Validate(string token, bool validateLifetime = true)
public async Task<ActionResult<User>> Validate(string token, bool validateLifetime = true)
{
if (jwtBearerService.TryValidateToken(token, validateLifetime, out var claimsPrincipal))
var result = await jwtBearerService.TryValidateTokenAsync(token, validateLifetime);

if (!result.IsValid)
{
return new User(claimsPrincipal.Identity!.Name);
return BadRequest();
}

return BadRequest();
return new User(result.Principal.Identity!.Name);
}

[HttpPost("refresh")]
[ProducesResponseType<LoginResponse>(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public ActionResult<LoginResponse> Refresh(string token, bool validateLifetime = true, DateTime? expiration = null)
public async Task<ActionResult<LoginResponse>> Refresh(string token, bool validateLifetime = true, DateTime? expiration = null)
{
var newToken = jwtBearerService.RefreshToken(token, validateLifetime, expiration);
var newToken = await jwtBearerService.RefreshTokenAsync(token, validateLifetime, expiration);
return new LoginResponse(newToken);
}
}
Expand Down
2 changes: 1 addition & 1 deletion samples/Controllers/JwtBearerSample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"JwtBearer": {
"SchemeName": "Bearer", // Default: Bearer
//"NameClaimType": "user_name", // Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
//"RoleClaimType": "role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
//"RoleClaimType": "role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
"SecurityKey": "YKgsOiwvDLJe42dyyL3FkhlMAzZZ2Cmr0FTpyLsPE5DA2afd6NbbCV3d5oHDG2rVBaDHH540EUmrzXPPk2LnfanCdERl4apucmu2Ev5oVgN6dGCr8MMxXIIyTaNmmXHSsaONo75UkxQvFtsm9Qsnsz3VxuNzsoqrzqBQdsDvClo1LcrRNNcTdKcvceq1G57PZNxOWFS749wnsqq7r17a9vvinTdYME2umo7DRn8XUiwbdOajCehJfqipIjwbcuoCIrCwwMizKSiidw5KXU7koVvUSV0UH3o4TWHsVBnt5B1os6oPKtCQ63CPqlwHB5Pet4mzA2lhaFROZXbStpigaRJf3J6AOwZurMbo3LhzCpPW6KZwkixMpwCb82ekZvL0tmfQA2LeWDL2esZ9N4N8w8CzxrZt4gyEfywBwsoFohC0ydVznDpwbgCg05ktuczX3FFcsXEErwtY2wu0or0TSrUSnzIrYP26dOOUh4qREPJ7ZnZ5NoQjOMcXkiThdMuy", // Required
"Algorithm": "HS256", // Default: HS256
"Issuers": [ "issuer" ], // Optional
Expand Down
2 changes: 2 additions & 0 deletions samples/MinimalApis/ApiKeySample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"Basic": {
"SchemeName": "Basic", // Default: Basic
// You can set a fixed user name and password for authentication. If you have a single credential, you can just use the plain properties:
//"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
"UserName": "marco",
"Password": "P@$$w0rd",
// Otherwise, you can create an array of Credentials:
Expand Down
18 changes: 10 additions & 8 deletions samples/MinimalApis/JwtBearerSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

var authApiGroup = app.MapGroup("api/auth");

authApiGroup.MapPost("login", (LoginRequest loginRequest, DateTime? expiration, IJwtBearerService jwtBearerService) =>
authApiGroup.MapPost("login", async (LoginRequest loginRequest, DateTime? expiration, IJwtBearerService jwtBearerService) =>
{
// Check for login rights...
Expand All @@ -83,7 +83,7 @@
claims.Add(new("scp", loginRequest.Scopes));
}
var token = jwtBearerService.CreateToken(loginRequest.UserName, claims, absoluteExpiration: expiration);
var token = await jwtBearerService.CreateTokenAsync(loginRequest.UserName, claims, absoluteExpiration: expiration);
return TypedResults.Ok(new LoginResponse(token));
})
.WithOpenApi(operation =>
Expand All @@ -92,20 +92,22 @@
return operation;
});

authApiGroup.MapPost("validate", Results<Ok<User>, BadRequest> (string token, bool validateLifetime, IJwtBearerService jwtBearerService) =>
authApiGroup.MapPost("validate", async Task<Results<Ok<User>, BadRequest>> (string token, bool validateLifetime, IJwtBearerService jwtBearerService) =>
{
if (jwtBearerService.TryValidateToken(token, validateLifetime, out var claimsPrincipal))
var result = await jwtBearerService.TryValidateTokenAsync(token, validateLifetime);
if (!result.IsValid)
{
return TypedResults.Ok(new User(claimsPrincipal.Identity!.Name));
return TypedResults.BadRequest();
}
return TypedResults.BadRequest();
return TypedResults.Ok(new User(result.Principal.Identity!.Name));
})
.WithOpenApi();

authApiGroup.MapPost("refresh", (string token, bool validateLifetime, DateTime? expiration, IJwtBearerService jwtBearerService) =>
authApiGroup.MapPost("refresh", async (string token, bool validateLifetime, DateTime? expiration, IJwtBearerService jwtBearerService) =>
{
var newToken = jwtBearerService.RefreshToken(token, validateLifetime, expiration);
var newToken = await jwtBearerService.RefreshTokenAsync(token, validateLifetime, expiration);
return TypedResults.Ok(new LoginResponse(newToken));
})
.WithOpenApi();
Expand Down
2 changes: 1 addition & 1 deletion samples/MinimalApis/JwtBearerSample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"JwtBearer": {
"SchemeName": "Bearer", // Default: Bearer
//"NameClaimType": "user_name", // Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
//"RoleClaimType": "role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
//"RoleClaimType": "user_role", // Default: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
"SecurityKey": "YKgsOiwvDLJe42dyyL3FkhlMAzZZ2Cmr0FTpyLsPE5DA2afd6NbbCV3d5oHDG2rVBaDHH540EUmrzXPPk2LnfanCdERl4apucmu2Ev5oVgN6dGCr8MMxXIIyTaNmmXHSsaONo75UkxQvFtsm9Qsnsz3VxuNzsoqrzqBQdsDvClo1LcrRNNcTdKcvceq1G57PZNxOWFS749wnsqq7r17a9vvinTdYME2umo7DRn8XUiwbdOajCehJfqipIjwbcuoCIrCwwMizKSiidw5KXU7koVvUSV0UH3o4TWHsVBnt5B1os6oPKtCQ63CPqlwHB5Pet4mzA2lhaFROZXbStpigaRJf3J6AOwZurMbo3LhzCpPW6KZwkixMpwCb82ekZvL0tmfQA2LeWDL2esZ9N4N8w8CzxrZt4gyEfywBwsoFohC0ydVznDpwbgCg05ktuczX3FFcsXEErwtY2wu0or0TSrUSnzIrYP26dOOUh4qREPJ7ZnZ5NoQjOMcXkiThdMuy", // Required
"Algorithm": "HS256", // Default: HS256
"Issuers": [ "issuer" ], // Optional
Expand Down
28 changes: 24 additions & 4 deletions src/SimpleAuthentication.Abstractions/ApiKey/ApiKeySettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

namespace SimpleAuthentication.ApiKey;

Expand Down Expand Up @@ -40,7 +41,7 @@ public class ApiKeySettings : AuthenticationSchemeOptions
/// <seealso cref="ApiKeyValue"/>
public string? UserName { get; set; }

private ICollection<ApiKey> apiKeys = new HashSet<ApiKey>();
private ICollection<ApiKey> apiKeys = [];
/// <summary>
/// The collection of valid API keys.
/// </summary>
Expand All @@ -52,14 +53,33 @@ public ICollection<ApiKey> ApiKeys
if (!string.IsNullOrWhiteSpace(ApiKeyValue) && !string.IsNullOrWhiteSpace(UserName))
{
// If necessary, add the API Key from the base properties.
apiKeys.Add(new ApiKey(ApiKeyValue, UserName));
apiKeys.Add(new(ApiKeyValue, UserName));
}

return apiKeys;
}

internal set => apiKeys = value ?? new HashSet<ApiKey>();
internal set => apiKeys = value ?? [];
}

/// <summary>
/// Gets or sets a <see cref="string"/> that defines the <see cref="ClaimsIdentity.NameClaimType"/>.
/// </summary>
/// <remarks>
/// Controls the value <see cref="ClaimsIdentity.Name"/> returns. It will return the first <see cref="Claim.Value"/> where the <see cref="Claim.Type"/> equals <see cref="NameClaimType"/>.
/// The default is <see cref="ClaimsIdentity.DefaultNameClaimType"/>.
/// </remarks>
public string NameClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;

/// <summary>
/// Gets or sets the <see cref="string"/> that defines the <see cref="ClaimsIdentity.RoleClaimType"/>.
/// </summary>
/// <remarks>
/// <para>Controls the results of <see cref="ClaimsPrincipal.IsInRole( string )"/>.</para>
/// <para>Each <see cref="Claim"/> where <see cref="Claim.Type"/> == <see cref="RoleClaimType"/> will be checked for a match against the 'string' passed to <see cref="ClaimsPrincipal.IsInRole(string)"/>.</para>
/// The default is <see cref="ClaimsIdentity.DefaultRoleClaimType"/>.
/// </remarks>
public string RoleClaimType { get; set; } = ClaimsIdentity.DefaultRoleClaimType;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;

namespace SimpleAuthentication.BasicAuthentication;

Expand Down Expand Up @@ -29,7 +30,7 @@ public class BasicAuthenticationSettings : AuthenticationSchemeOptions
/// <seealso cref="IBasicAuthenticationValidator"/>
public string? Password { get; set; }

private ICollection<Credential> credentials = new HashSet<Credential>();
private ICollection<Credential> credentials = [];
/// <summary>
/// The collection of authorization credentials.
/// </summary>
Expand All @@ -47,8 +48,28 @@ public ICollection<Credential> Credentials
return credentials;
}

internal set => credentials = value ?? new HashSet<Credential>();
internal set => credentials = value ?? [];
}

/// <summary>
/// Gets or sets a <see cref="string"/> that defines the <see cref="ClaimsIdentity.NameClaimType"/>.
/// </summary>
/// <remarks>
/// Controls the value <see cref="ClaimsIdentity.Name"/> returns. It will return the first <see cref="Claim.Value"/> where the <see cref="Claim.Type"/> equals <see cref="NameClaimType"/>.
/// The default is <see cref="ClaimsIdentity.DefaultNameClaimType"/>.
/// </remarks>
public string NameClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;

/// <summary>
/// Gets or sets the <see cref="string"/> that defines the <see cref="ClaimsIdentity.RoleClaimType"/>.
/// </summary>
/// <remarks>
/// <para>Controls the results of <see cref="ClaimsPrincipal.IsInRole( string )"/>.</para>
/// <para>Each <see cref="Claim"/> where <see cref="Claim.Type"/> == <see cref="RoleClaimType"/> will be checked for a match against the 'string' passed to <see cref="ClaimsPrincipal.IsInRole(string)"/>.</para>
/// The default is <see cref="ClaimsIdentity.DefaultRoleClaimType"/>.
/// </remarks>
public string RoleClaimType { get; set; } = ClaimsIdentity.DefaultRoleClaimType;

}

/// <summary>
Expand Down
Loading

0 comments on commit f9b2d06

Please sign in to comment.