Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Lint Code Base](https://github.com/marcominerva/SimpleAuthentication/actions/workflows/linter.yml/badge.svg)](https://github.com/marcominerva/SimpleAuthentication/actions/workflows/linter.yml)
[![CodeQL](https://github.com/marcominerva/SimpleAuthentication/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/marcominerva/SimpleAuthentication/actions/workflows/github-code-scanning/codeql)
[![Nuget](https://img.shields.io/nuget/v/SimpleAuthenticationTools)](https://www.nuget.org/packages/SimpleAuthenticationTools)
[![Nuget](https://img.shields.io/nuget/dt/SimpleAuthenticationTools)](https://www.nuget.org/packages/SimpleAuthenticationTools)
[![NuGet](https://img.shields.io/nuget/dt/SimpleAuthenticationTools)](https://www.nuget.org/packages/SimpleAuthenticationTools)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/marcominerva/SimpleAuthentication/blob/master/LICENSE)

A library to easily integrate Authentication in ASP.NET Core projects. Currently it supports JWT Bearer, API Key and Basic Authentication in both Controller-based and Minimal API projects.
Expand Down
4 changes: 2 additions & 2 deletions samples/Controllers/ApiKeySample/ApiKeySample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions samples/Controllers/JwtBearerSample/JwtBearerSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions samples/MinimalApis/ApiKeySample/ApiKeySample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 7 additions & 2 deletions samples/MinimalApis/ApiKeySample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,15 @@ public class CustomApiKeyValidator : IApiKeyValidator
{
public Task<ApiKeyValidationResult> ValidateAsync(string apiKey)
{
var claims = new[]
{
new Claim(ClaimTypes.Role, "User")
};

var result = apiKey switch
{
"ArAilHVOoL3upX78Cohq" => ApiKeyValidationResult.Success("User 1"),
"DiUU5EqImTYkxPDAxBVS" => ApiKeyValidationResult.Success("User 2"),
"ArAilHVOoL3upX78Cohq" => ApiKeyValidationResult.Success("User 1", claims),
"DiUU5EqImTYkxPDAxBVS" => ApiKeyValidationResult.Success("User 2", claims),
_ => ApiKeyValidationResult.Fail("Invalid User")
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions samples/MinimalApis/JwtBearerSample/JwtBearerSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="SimpleAuthenticationTools.Abstractions" Version="3.1.12" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.1.7" />
<PackageReference Include="SimpleAuthenticationTools.Abstractions" Version="3.1.13" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
43 changes: 24 additions & 19 deletions src/SimpleAuthentication/ApiKey/ApiKeyAuthenticationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Security.Claims;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -33,37 +35,40 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
var validator = serviceProvider.GetService<IApiKeyValidator>() ?? throw new InvalidOperationException("There isn't a default value for API Key and no custom validator has been provided");

var validationResult = await validator.ValidateAsync(value.ToString());
if (validationResult.Succeeded)
if (!validationResult.Succeeded)
{
return CreateAuthenticationSuccessResult(validationResult.UserName, validationResult.Claims);
return AuthenticateResult.Fail(validationResult.FailureMessage);
}

return AuthenticateResult.Fail(validationResult.FailureMessage);
return CreateAuthenticationSuccessResult(validationResult.UserName, validationResult.Claims);
}

var apiKey = apiKeys.FirstOrDefault(c => c.Value == value);
if (apiKey is not null)
var providedApiKey = value.ToString();
var apiKey = apiKeys.FirstOrDefault(a => CryptographicOperations.FixedTimeEquals(MemoryMarshal.AsBytes(a.Value.AsSpan()), MemoryMarshal.AsBytes(providedApiKey.AsSpan())));

if (apiKey is null)
{

return AuthenticateResult.Fail("Invalid API Key");
}
Comment on lines +46 to +53

var claims = new List<Claim>();
if (apiKey.Roles is not null)
{
var claims = new List<Claim>();
if (apiKey.Roles is not null)
foreach (var role in apiKey.Roles)
{
foreach (var role in apiKey.Roles)
{
claims.Add(new(Options.RoleClaimType, role));
}
claims.Add(new(Options.RoleClaimType, role));
}

return CreateAuthenticationSuccessResult(apiKey.UserName, claims);
}

return AuthenticateResult.Fail("Invalid API Key");
return CreateAuthenticationSuccessResult(apiKey.UserName, claims);

AuthenticateResult CreateAuthenticationSuccessResult(string userName, IList<Claim>? claims = null)
AuthenticateResult CreateAuthenticationSuccessResult(string userName, IEnumerable<Claim>? claims = null)
{
claims ??= [];
claims.Update(Options.NameClaimType, userName);
var claimsList = claims?.ToList() ?? [];
claimsList.Update(Options.NameClaimType, userName);

var identity = new ClaimsIdentity(claims, Scheme.Name, Options.NameClaimType, Options.RoleClaimType);
var identity = new ClaimsIdentity(claimsList, Scheme.Name, Options.NameClaimType, Options.RoleClaimType);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Security.Claims;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -47,37 +49,39 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
var validator = serviceProvider.GetService<IBasicAuthenticationValidator>() ?? throw new InvalidOperationException("There isn't a default user name and password for authentication and no custom validator has been provided");

var validationResult = await validator.ValidateAsync(userName, password);
if (validationResult.Succeeded)
if (!validationResult.Succeeded)
{
return CreateAuthenticationSuccessResult(validationResult.UserName, validationResult.Claims);
return AuthenticateResult.Fail(validationResult.FailureMessage);
}

return AuthenticateResult.Fail(validationResult.FailureMessage);
return CreateAuthenticationSuccessResult(validationResult.UserName, validationResult.Claims);
}

var credential = credentials.FirstOrDefault(c => c.UserName == userName && c.Password == password);
if (credential is not null)
var credential = credentials.FirstOrDefault(c => CryptographicOperations.FixedTimeEquals(MemoryMarshal.AsBytes(c.UserName.AsSpan()), MemoryMarshal.AsBytes(userName.AsSpan()))
&& CryptographicOperations.FixedTimeEquals(MemoryMarshal.AsBytes(c.Password.AsSpan()), MemoryMarshal.AsBytes(password.AsSpan())));
Comment on lines +60 to +61

if (credential is null)
{
return AuthenticateResult.Fail("Invalid user name or password");
}

var claims = new List<Claim>();
if (credential.Roles is not null)
{
var claims = new List<Claim>();
if (credential.Roles is not null)
foreach (var role in credential.Roles)
{
foreach (var role in credential.Roles)
{
claims.Add(new(Options.RoleClaimType, role));
}
claims.Add(new(Options.RoleClaimType, role));
}

return CreateAuthenticationSuccessResult(credential.UserName, claims);
}

return AuthenticateResult.Fail("Invalid user name or password");
return CreateAuthenticationSuccessResult(credential.UserName, claims);

AuthenticateResult CreateAuthenticationSuccessResult(string userName, IList<Claim>? claims = null)
AuthenticateResult CreateAuthenticationSuccessResult(string userName, IEnumerable<Claim>? claims = null)
{
claims ??= [];
claims.Update(Options.NameClaimType, userName);
var claimsList = claims?.ToList() ?? [];
claimsList.Update(Options.NameClaimType, userName);

var identity = new ClaimsIdentity(claims, Scheme.Name, Options.NameClaimType, Options.RoleClaimType);
var identity = new ClaimsIdentity(claimsList, Scheme.Name, Options.NameClaimType, Options.RoleClaimType);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

Expand All @@ -86,6 +90,6 @@ AuthenticateResult CreateAuthenticationSuccessResult(string userName, IList<Clai
}
}

[GeneratedRegex(@"Basic (.*)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "it-IT")]
[GeneratedRegex(@"Basic (.*)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant)]
private static partial Regex BasicAuthorizationHeaderRegex();
}
16 changes: 8 additions & 8 deletions src/SimpleAuthentication/JwtBearer/JwtBearerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class JwtBearerService(IOptions<JwtBearerSettings> jwtBearerSettingsOptio
protected JwtBearerSettings JwtBearerSettings { get; } = jwtBearerSettingsOptions?.Value ?? throw new ArgumentNullException(nameof(jwtBearerSettingsOptions));

/// <inheritdoc />
public virtual Task<string> CreateTokenAsync(string userName, IList<Claim>? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null)
public virtual Task<string> CreateTokenAsync(string userName, IEnumerable<Claim>? claims = null, string? issuer = null, string? audience = null, DateTime? absoluteExpiration = null)
{
var now = DateTime.UtcNow;

Expand All @@ -27,13 +27,13 @@ public virtual Task<string> CreateTokenAsync(string userName, IList<Claim>? clai
throw new ArgumentException("The expiration date must be greater than or equal to the current date and time.", nameof(absoluteExpiration));
}

claims ??= [];
claims.Update(JwtBearerSettings.NameClaimType, userName);
claims.Update(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString());
var claimsList = claims?.ToList() ?? [];
claimsList.Update(JwtBearerSettings.NameClaimType, userName);
claimsList.Update(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString());

var securityTokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims, JwtBearerSettings.SchemeName, JwtBearerSettings.NameClaimType, JwtBearerSettings.RoleClaimType),
Subject = new ClaimsIdentity(claimsList, JwtBearerSettings.SchemeName, JwtBearerSettings.NameClaimType, JwtBearerSettings.RoleClaimType),
Issuer = issuer ?? JwtBearerSettings.Issuers?.FirstOrDefault(),
Audience = audience ?? JwtBearerSettings.Audiences?.FirstOrDefault(),
IssuedAt = now,
Expand Down Expand Up @@ -63,9 +63,9 @@ public virtual async Task<ClaimsPrincipal> ValidateTokenAsync(string token, bool
AuthenticationType = JwtBearerSettings.SchemeName,
NameClaimType = JwtBearerSettings.NameClaimType,
RoleClaimType = JwtBearerSettings.RoleClaimType,
ValidateIssuer = JwtBearerSettings.Issuers?.Any() ?? false,
ValidateIssuer = JwtBearerSettings.Issuers?.Length > 0,
ValidIssuers = JwtBearerSettings.Issuers,
ValidateAudience = JwtBearerSettings.Audiences?.Any() ?? false,
ValidateAudience = JwtBearerSettings.Audiences?.Length > 0,
ValidAudiences = JwtBearerSettings.Audiences,
Comment thread
marcominerva marked this conversation as resolved.
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtBearerSettings.SecurityKey)),
Expand All @@ -89,7 +89,7 @@ public virtual async Task<ClaimsPrincipal> ValidateTokenAsync(string token, bool
public virtual async Task<string> RefreshTokenAsync(string token, bool validateLifetime, DateTime? absoluteExpiration = null)
{
var principal = await ValidateTokenAsync(token, validateLifetime);
var claims = (principal.Identity as ClaimsIdentity)!.Claims.ToList();
var claims = (principal.Identity as ClaimsIdentity)!.Claims;

var userName = claims.First(c => c.Type == JwtBearerSettings.NameClaimType).Value;
var issuer = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Iss)?.Value;
Expand Down
6 changes: 3 additions & 3 deletions src/SimpleAuthentication/SimpleAuthentication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.15" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.17" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="SimpleAuthenticationTools.Abstractions" Version="3.1.12" />
<PackageReference Include="SimpleAuthenticationTools.Abstractions" Version="3.1.13" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading