Skip to content

Commit

Permalink
Added support to have ApiKey in request route identified by route pat…
Browse files Browse the repository at this point in the history
…tern key from netcoreapp3.0 onwards
  • Loading branch information
mihirdilip committed Jan 29, 2024
1 parent 066f2f3 commit e639bcb
Show file tree
Hide file tree
Showing 13 changed files with 491 additions and 39 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Mihir Dilip
Copyright (c) 2024 Mihir Dilip

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AspNetCore.Authentication.ApiKey
Easy to use and very light weight Microsoft style API Key Authentication Implementation for ASP.NET Core. It can be setup so that it can accept API Key either in Header, Authorization Header, QueryParams or HeaderOrQueryParams.
Easy to use and very light weight Microsoft style API Key Authentication Implementation for ASP.NET Core. It can be setup so that it can accept API Key either in Header, Authorization Header, QueryParams, HeaderOrQueryParams or RouteValues.

[View On GitHub](https://github.com/mihirdilip/aspnetcore-authentication-apikey)

Expand Down Expand Up @@ -190,7 +190,7 @@ class ApiKey : IApiKey
## Configuration (ApiKeyOptions)

### KeyName
Required to be set. It is the name of the header if it is setup as in header or the name of the query parameter if set as in query_params.
Required to be set. It is the name of the header if it is setup as in header or the name of the query parameter if set as in query_params or the name of the route key if set as in route_values.

### Realm
Required to be set if SuppressWWWAuthenticateHeader is not set to true. It is used with WWW-Authenticate response header when challenging un-authenticated requests.
Expand Down Expand Up @@ -263,6 +263,10 @@ WWW-Authenticate challenge header will contain parameter `in="query_params"`.
Adds ApiKey authentication which can handle the api key in the either Header, Authorization Header or Query Parameter.
WWW-Authenticate challenge header will contain parameter `in="header_or_query_params"`.

### AddApiKeyInRouteValues
Adds ApiKey authentication which can handle the api key in the url route identified by the route pattern key.
WWW-Authenticate challenge header will contain parameter `in="route_values"`.

<br/>
<br/>

Expand All @@ -276,7 +280,7 @@ where,

- &lt;REALM&gt; == *ApiKeyOptions.Realm*

- <IN_PARAMERTER> == Depending on the [extension method](#extension-methods) used, it could be either of *header*, *authorization_header*, *query_params*, *header_or_query_params*
- <IN_PARAMERTER> == Depending on the [extension method](#extension-methods) used, it could be either of *header*, *authorization_header*, *query_params*, *header_or_query_params*, *route_values*

- <KEY_NAME> == *ApiKeyOptions.KeyName*

Expand Down Expand Up @@ -380,6 +384,7 @@ public void ConfigureServices(IServiceCollection services)
## Release Notes
| Version | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Notes |
|---------|-------|
|8.0.1 | <ul><li>Added support to have ApiKey in request route identified by route pattern key from netcoreapp3.0 onwards [#41](https://github.com/mihirdilip/aspnetcore-authentication-apikey/issues/41)</li><li>Readme updated</li></ul> |
|8.0.0 | <ul><li>net8.0 support added</li><li>Sample project for net8.0 added</li><li>ApiKeySamplesClient.http file added for testing sample projects</li><li>Readme updated</li></ul> |
|7.0.0 | <ul><li>net7.0 support added</li><li>Information log on handler is changed to Debug log when API Key is not found on the request</li><li>Added package validations</li><li>Sample project for net7.0 added</li><li>Readme updated</li><li>Readme added to package</li></ul> |
|6.0.1 | <ul><li>net6.0 support added</li><li>Information log on handler is changed to Debug log when IgnoreAuthenticationIfAllowAnonymous is enabled</li><li>Sample project added</li><li>Readme updated</li><li>Copyright year updated on License</li></ul> |
Expand Down
143 changes: 130 additions & 13 deletions src/AspNetCore.Authentication.ApiKey/ApiKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,17 +363,17 @@ public static AuthenticationBuilder AddApiKeyInQueryParams<TApiKeyProvider>(this
public static AuthenticationBuilder AddApiKeyInQueryParams<TApiKeyProvider>(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKey<TApiKeyProvider, ApiKeyInQueryParamsHandler>(authenticationScheme, displayName, configureOptions);

#endregion // API Key - In Query Parameters
#endregion // API Key - In Query Parameters

#region API Key - In Header Or Query Parameters
#region API Key - In Header Or Query Parameters

/// <summary>
/// Adds API Key - In Header Or Query Parameters authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the <see cref="ApiKeyOptions.Events"/>.
/// </summary>
/// <param name="builder"></param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInHeaderOrQueryParams(this AuthenticationBuilder builder)
/// <summary>
/// Adds API Key - In Header Or Query Parameters authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the <see cref="ApiKeyOptions.Events"/>.
/// </summary>
/// <param name="builder"></param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInHeaderOrQueryParams(this AuthenticationBuilder builder)
=> builder.AddApiKeyInHeaderOrQueryParams(ApiKeyDefaults.AuthenticationScheme);

/// <summary>
Expand Down Expand Up @@ -480,10 +480,127 @@ public static AuthenticationBuilder AddApiKeyInHeaderOrQueryParams<TApiKeyProvid
public static AuthenticationBuilder AddApiKeyInHeaderOrQueryParams<TApiKeyProvider>(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKey<TApiKeyProvider, ApiKeyInHeaderOrQueryParamsHandler>(authenticationScheme, displayName, configureOptions);

#endregion // API Key - In Header Or Query Parameters


private static AuthenticationBuilder AddApiKey<TApiKeyHandler>(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions)
#endregion // API Key - In Header Or Query Parameters

#region API Key - In Route Values
#if NETCOREAPP3_0_OR_GREATER
/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the <see cref="ApiKeyOptions.Events"/>.
/// </summary>
/// <param name="builder"></param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues(this AuthenticationBuilder builder)
=> builder.AddApiKeyInRouteValues(ApiKeyDefaults.AuthenticationScheme);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the <see cref="ApiKeyOptions.Events"/>.
/// </summary>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues(this AuthenticationBuilder builder, string authenticationScheme)
=> builder.AddApiKeyInRouteValues(authenticationScheme, configureOptions: null);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the Events property on <paramref name="configureOptions"/>.
/// </summary>
/// <param name="builder"></param>
/// <param name="configureOptions">The configure options.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues(this AuthenticationBuilder builder, Action<ApiKeyOptions> configureOptions)
=> builder.AddApiKeyInRouteValues(ApiKeyDefaults.AuthenticationScheme, configureOptions);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the Events property on <paramref name="configureOptions"/>.
/// </summary>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">The configure options.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues(this AuthenticationBuilder builder, string authenticationScheme, Action<ApiKeyOptions> configureOptions)
=> builder.AddApiKeyInRouteValues(authenticationScheme, displayName: null, configureOptions: configureOptions);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project.
/// <see cref="ApiKeyEvents.OnValidateKey"/> delegate must be set on the Events property on <paramref name="configureOptions"/>.
/// </summary>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="displayName">The display name.</param>
/// <param name="configureOptions">The configure options.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions)
=> builder.AddApiKey<ApiKeyInRouteValuesHandler>(authenticationScheme, displayName, configureOptions);





/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project. It takes a implementation of <see cref="IApiKeyProvider"/> as type parameter.
/// If <see cref="ApiKeyEvents.OnValidateKey"/> delegate is set on the <see cref="ApiKeyOptions.Events"/> then it will be used instead of implementation of <see cref="IApiKeyProvider"/>.
/// </summary>
/// <typeparam name="TApiKeyProvider"></typeparam>
/// <param name="builder"></param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues<TApiKeyProvider>(this AuthenticationBuilder builder) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKeyInRouteValues<TApiKeyProvider>(ApiKeyDefaults.AuthenticationScheme);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project. It takes a implementation of <see cref="IApiKeyProvider"/> as type parameter.
/// If <see cref="ApiKeyEvents.OnValidateKey"/> delegate is set on the <see cref="ApiKeyOptions.Events"/> then it will be used instead of implementation of <see cref="IApiKeyProvider"/>.
/// </summary>
/// <typeparam name="TApiKeyProvider"></typeparam>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues<TApiKeyProvider>(this AuthenticationBuilder builder, string authenticationScheme) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKeyInRouteValues<TApiKeyProvider>(authenticationScheme, configureOptions: null);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project. It takes a implementation of <see cref="IApiKeyProvider"/> as type parameter.
/// If <see cref="ApiKeyEvents.OnValidateKey"/> delegate is set on the Events property on <paramref name="configureOptions"/> then it will be used instead of implementation of <see cref="IApiKeyProvider"/>.
/// </summary>
/// <typeparam name="TApiKeyProvider"></typeparam>
/// <param name="builder"></param>
/// <param name="configureOptions">The <see cref="ApiKeyOptions"/>.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues<TApiKeyProvider>(this AuthenticationBuilder builder, Action<ApiKeyOptions> configureOptions) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKeyInRouteValues<TApiKeyProvider>(ApiKeyDefaults.AuthenticationScheme, configureOptions);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project. It takes a implementation of <see cref="IApiKeyProvider"/> as type parameter.
/// If <see cref="ApiKeyEvents.OnValidateKey"/> delegate is set on the Events property on <paramref name="configureOptions"/> then it will be used instead of implementation of <see cref="IApiKeyProvider"/>.
/// </summary>
/// <typeparam name="TApiKeyProvider"></typeparam>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">The <see cref="ApiKeyOptions"/>.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues<TApiKeyProvider>(this AuthenticationBuilder builder, string authenticationScheme, Action<ApiKeyOptions> configureOptions) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKeyInRouteValues<TApiKeyProvider>(authenticationScheme, displayName: null, configureOptions: configureOptions);

/// <summary>
/// Adds API Key - In Route Values authentication scheme to the project. It takes a implementation of <see cref="IApiKeyProvider"/> as type parameter.
/// If <see cref="ApiKeyEvents.OnValidateKey"/> delegate is set on the Events property on <paramref name="configureOptions"/> then it will be used instead of implementation of <see cref="IApiKeyProvider"/>.
/// </summary>
/// <typeparam name="TApiKeyProvider"></typeparam>
/// <param name="builder"></param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="displayName">The display name.</param>
/// <param name="configureOptions">The <see cref="ApiKeyOptions"/>.</param>
/// <returns>The instance of <see cref="AuthenticationBuilder"/></returns>
public static AuthenticationBuilder AddApiKeyInRouteValues<TApiKeyProvider>(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions) where TApiKeyProvider : class, IApiKeyProvider
=> builder.AddApiKey<TApiKeyProvider, ApiKeyInRouteValuesHandler>(authenticationScheme, displayName, configureOptions);
#endif
#endregion // API Key - In Route Values


private static AuthenticationBuilder AddApiKey<TApiKeyHandler>(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ApiKeyOptions> configureOptions)
where TApiKeyHandler : AuthenticationHandler<ApiKeyOptions>
{
// Adds post configure options to the pipeline.
Expand Down
16 changes: 1 addition & 15 deletions src/AspNetCore.Authentication.ApiKey/ApiKeyHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,21 +225,7 @@ private string GetWwwAuthenticateSchemeName()
: Scheme.Name;
}

private string GetWwwAuthenticateInParameter()
{
var handlerType = this.GetType();

if (handlerType == typeof(ApiKeyInAuthorizationHeaderHandler))
return "authorization_header";
if (handlerType == typeof(ApiKeyInHeaderHandler))
return "header";
if (handlerType == typeof(ApiKeyInQueryParamsHandler))
return "query_params";
if (handlerType == typeof(ApiKeyInHeaderOrQueryParamsHandler))
return "header_or_query_params";

throw new NotImplementedException($"No parameter name defined for {handlerType.FullName}.");
}
protected abstract string GetWwwAuthenticateInParameter();

private bool IgnoreAuthenticationIfAllowAnonymous()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace AspNetCore.Authentication.ApiKey
{
public class ApiKeyInAuthorizationHeaderHandler : ApiKeyHandlerBase
{
private const string WwwAuthenticateInParameter = "authorization_header";
protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter;

#if NET8_0_OR_GREATER
protected ApiKeyInAuthorizationHeaderHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
Expand Down Expand Up @@ -43,5 +46,5 @@ protected override Task<string> ParseApiKeyAsync()
// No ApiKey found
return Task.FromResult(string.Empty);
}
}
}
}
3 changes: 3 additions & 0 deletions src/AspNetCore.Authentication.ApiKey/ApiKeyInHeaderHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace AspNetCore.Authentication.ApiKey
{
public class ApiKeyInHeaderHandler : ApiKeyHandlerBase
{
private const string WwwAuthenticateInParameter = "header";
protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter;

#if NET8_0_OR_GREATER
protected ApiKeyInHeaderHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace AspNetCore.Authentication.ApiKey
{
public class ApiKeyInHeaderOrQueryParamsHandler : ApiKeyHandlerBase
{
private const string WwwAuthenticateInParameter = "header_or_query_params";
protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter;

#if NET8_0_OR_GREATER
protected ApiKeyInHeaderOrQueryParamsHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace AspNetCore.Authentication.ApiKey
{
public class ApiKeyInQueryParamsHandler : ApiKeyHandlerBase
{
private const string WwwAuthenticateInParameter = "query_params";
protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter;

#if NET8_0_OR_GREATER
protected ApiKeyInQueryParamsHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
Expand Down
44 changes: 44 additions & 0 deletions src/AspNetCore.Authentication.ApiKey/ApiKeyInRouteValuesHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Mihir Dilip. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for license information.
#if NETCOREAPP3_0_OR_GREATER

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace AspNetCore.Authentication.ApiKey
{
public class ApiKeyInRouteValuesHandler : ApiKeyHandlerBase
{
private const string WwwAuthenticateInParameter = "route_values";
protected override string GetWwwAuthenticateInParameter() => WwwAuthenticateInParameter;

#if NET8_0_OR_GREATER
protected ApiKeyInRouteValuesHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
{
}

[Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")]
#endif
public ApiKeyInRouteValuesHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}

protected override Task<string> ParseApiKeyAsync()
{
if (Request.RouteValues.TryGetValue(Options.KeyName, out var value) && value != null && value.GetType() == typeof(string))
{
return Task.FromResult(value.ToString());
}

// No ApiKey query parameter found
return Task.FromResult(string.Empty);
}
}
}
#endif
Loading

0 comments on commit e639bcb

Please sign in to comment.