Skip to content

Commit

Permalink
Added various contexts to authentication events (#12)
Browse files Browse the repository at this point in the history
* Added various contexts to authentication events

* Fixed code doc

* Upped major version due to breaking changes
  • Loading branch information
dnmh-psc committed May 11, 2023
1 parent 32fc031 commit 8a79086
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 20 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ namespace MyApp
}
}

public class ApiKeyAuthenticationService : IApiKeyAuthenticationService
public class ApiKeyAuthenticationService : SimpleApiKeyAuthenticationServiceBase
{
public bool ValidateApiKey(string apiKey)
public bool Validate(ValidationContext context)
{
return apiKey == "YOUR_API_KEY";
return context.ApiKey == "YOUR_API_KEY";
}
}
```
Expand Down
5 changes: 3 additions & 2 deletions Source/AuthenticationHandler/ApiKeyAuthenticationEvents.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Security.Claims;
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler;

Expand All @@ -10,11 +11,11 @@ public class ApiKeyAuthenticationEvents
/// <summary>
/// Event that is raised when authentication fails, with the exception as parameter.
/// </summary>
public Func<Exception, Task> OnAuthenticationFailed { get; set; } = _ => Task.CompletedTask;
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = _ => Task.CompletedTask;

/// <summary>
/// Event that is raised when authentication succeeds, with a <see cref="ClaimsPrincipal"/> as parameter.
/// </summary>
public Func<ClaimsPrincipal, Task> OnAuthenticationSuccess { get; set; } = _ => Task.CompletedTask;
public Func<AuthenticationSuccessContext, Task> OnAuthenticationSuccess { get; set; } = _ => Task.CompletedTask;

}
9 changes: 5 additions & 4 deletions Source/AuthenticationHandler/ApiKeyAuthenticationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication;
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -62,7 +63,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
return AuthenticateResult.NoResult();
}

var result = await _authenticationService.ValidateAsync(apiKey!);
var result = await _authenticationService.ValidateAsync(new ValidationContext(Context, Scheme, Options, apiKey!));

if (result is null)
{
Expand All @@ -71,7 +72,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()

if (Events is not null)
{
await Events.OnAuthenticationSuccess(result);
await Events.OnAuthenticationSuccess(new AuthenticationSuccessContext(Context, Scheme, Options, result));
}
var ticket = new AuthenticationTicket(result, Scheme.Name);
return AuthenticateResult.Success(ticket);
Expand Down Expand Up @@ -155,7 +156,7 @@ private async Task<AuthenticateResult> FailAuthentication(Exception ex)
{
if (Events is not null)
{
await Events.OnAuthenticationFailed(ex);
await Events.OnAuthenticationFailed(new AuthenticationFailedContext(Context, Scheme, Options, ex));
}
return AuthenticateResult.Fail(ex.Message);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

/// <summary>
/// The context associated with failed authentication
/// </summary>
public class AuthenticationFailedContext : ResultContext<ApiKeyAuthenticationOptions>
{
/// <summary>
/// The optional exception that occured when authentication failed.
/// </summary>
public Exception? Exception { get; }

/// <inheritdoc/>
public AuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, ApiKeyAuthenticationOptions options, Exception? exception)
: base(context, scheme, options)
{
Exception = exception;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

/// <summary>
/// The context associated with authentication success
/// </summary>
public class AuthenticationSuccessContext : ResultContext<ApiKeyAuthenticationOptions>
{
/// <summary>
/// The claims principle associated with the authentication success
/// </summary>
public ClaimsPrincipal ClaimsPrincipal { get; }

/// <inheritdoc/>
public AuthenticationSuccessContext(HttpContext context, AuthenticationScheme scheme, ApiKeyAuthenticationOptions options, ClaimsPrincipal claimsPrincipal)
: base(context, scheme, options)
{
ClaimsPrincipal = claimsPrincipal;
}
}
22 changes: 22 additions & 0 deletions Source/AuthenticationHandler/Context/ValidationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

/// <summary>
/// The context associated with validiting the api key.
/// </summary>
public class ValidationContext : BaseContext<ApiKeyAuthenticationOptions>
{
/// <summary>
/// The api key that needs validation
/// </summary>
public string ApiKey { get; }

/// <inheritdoc/>
public ValidationContext(HttpContext context, AuthenticationScheme scheme, ApiKeyAuthenticationOptions options, string apiKey)
: base(context, scheme, options)
{
ApiKey = apiKey;
}
}
5 changes: 3 additions & 2 deletions Source/AuthenticationHandler/IApiKeyAuthenticationService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Security.Claims;
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler;

Expand All @@ -10,7 +11,7 @@ public interface IApiKeyAuthenticationService
/// <summary>
/// Validates a given api key.
/// </summary>
/// <param name="apiKey">The api key to validate</param>
/// <param name="context">The context for the validation, where the api key that needs validation also can be found through <see cref="ValidationContext.ApiKey"/></param>
/// <returns>A non-null <see cref="ClaimsPrincipal"/> indicating success or null indicating failure</returns>
Task<ClaimsPrincipal?> ValidateAsync(string apiKey);
Task<ClaimsPrincipal?> ValidateAsync(ValidationContext context);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Security.Claims;
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;

namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler;

Expand All @@ -9,24 +10,24 @@ namespace Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler;
public abstract class SimpleApiKeyAuthenticationServiceBase : IApiKeyAuthenticationService
{
/// <inheritdoc/>
public Task<ClaimsPrincipal?> ValidateAsync(string apiKey)
public Task<ClaimsPrincipal?> ValidateAsync(ValidationContext context)
{
ClaimsPrincipal? claimsPrinciple = null;

if (ValidateKey(apiKey))
if (Validate(context))
{
claimsPrinciple = new ClaimsPrincipal(new ClaimsIdentity("ApiKey"));
claimsPrinciple = new ClaimsPrincipal(new ClaimsIdentity(context.Scheme.Name));
}

return Task.FromResult(claimsPrinciple);
}

/// <summary>
/// Validates the given <paramref name="apiKey"/>.
/// Validates the given <paramref name="context"/>.
/// </summary>
/// <param name="apiKey">The api key to validate</param>
/// <param name="context">The context for the validation, where the api key that needs validation also can be found through <see cref="ValidationContext.ApiKey"/></param>
/// <returns>True if the given api key is valid. False if not.</returns>
protected abstract bool ValidateKey(string apiKey);
protected abstract bool Validate(ValidationContext context);
}

/// <summary>
Expand All @@ -49,5 +50,5 @@ public SimpleApiKeyAuthenticationService(string validApiKey)
}

/// <inheritdoc/>
protected override bool ValidateKey(string apiKey) => _validApiKey == apiKey;
protected override bool Validate(ValidationContext context) => _validApiKey == context.ApiKey;
}
2 changes: 1 addition & 1 deletion Source/Dnmh.Security.ApiKeyAuthentication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/DetNordjyskeMediehus/Dnmh.Security.ApiKeyAuthentication</PackageProjectUrl>
<Title>.Net ApiKey Authentication</Title>
<Version>1.1.0</Version>
<Version>2.0.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>A .NET Core library that provides API key authentication for your web applications. With this library, you can require API keys to access your API endpoints and secure your application against unauthorized access. The library can also be integrated with Swagger UI to provide a seamless authentication experience.</Description>
<PackageTags>authentication dotnet .Net dotnetcore .NetCore apikey apikey-authentication swagger swagger-ui</PackageTags>
Expand Down
3 changes: 2 additions & 1 deletion Tests/MockHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler;
using Dnmh.Security.ApiKeyAuthentication.AuthenticationHandler.Context;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -41,7 +42,7 @@ public static Mock<HttpContext> CreateMockHttpContextWithMockRequest(Dictionary<
public static ApiKeyAuthenticationHandler CreateApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, bool apiKeyValidationResult = true)
{
var mockService = new Mock<SimpleApiKeyAuthenticationServiceBase>();
mockService.Protected().Setup<bool>("ValidateKey", ItExpr.IsAny<string>()).Returns(apiKeyValidationResult);
mockService.Protected().Setup<bool>("Validate", ItExpr.IsAny<ValidationContext>()).Returns(apiKeyValidationResult);

var mockLogger = new Mock<ILogger>();
var mockLoggerFactory = new Mock<ILoggerFactory>();
Expand Down

0 comments on commit 8a79086

Please sign in to comment.