From a19c3bcff40a5094869821f37cc6d7ae84e2deff Mon Sep 17 00:00:00 2001 From: Liam Pietralla Date: Tue, 18 Feb 2025 08:52:04 +1100 Subject: [PATCH] added api key auth docs --- .vitepress/config.mts | 1 + docs/dotnet/api-key-auth.md | 136 ++++++++++++++++++++++++++++++++++++ docs/dotnet/index.md | 3 +- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/dotnet/api-key-auth.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 87af42a..06c2b42 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -36,6 +36,7 @@ export default defineConfig({ { text: 'Google Sign in without Identity', link: '/dotnet/google-sign-in-without-identity' }, { text: 'Service Testing', link: '/dotnet/service-testing' }, { text: 'Controller Testing', link: '/dotnet/controller-testing' }, + { text: 'API Key Authentication', link: '/dotnet/api-key-auth'} ] }, { diff --git a/docs/dotnet/api-key-auth.md b/docs/dotnet/api-key-auth.md new file mode 100644 index 0000000..73f3506 --- /dev/null +++ b/docs/dotnet/api-key-auth.md @@ -0,0 +1,136 @@ +# API Key Auth + +Simple API Key authentication is a great option when building public facing APIs without strict security requirements, but you would rather not leave open. Think syncs, long running jobs or other non-critical operations. + +## Configuration + +This example stores the ApiKey in the `appsettings.json` file. You can also store it in a database, environment variable, or any other configuration source. + +::: code-group + +```json[appsettings.json] +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ApiKey": "ThisIsMySecretKey", + // ... +} +``` + +::: + +## Filter + +The logic for the api key authentication is a simple Authorization filter. It checks the `ApiKey` header against the configured value. + +Start by storing the header name in a constants file or similar: + +::: code-group + +```csharp[Constants.cs] +namespace ApiKeyAuthDemo.Core +{ + public static class Constants + { + public const string API_KEY_HEADER_NAME = "X-API-KEY"; + } +} +``` + +::: + +Then create the filter: + +::: code-group + +```csharp[ApiKeyAuthorizeAttribute.cs] +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace ApiKeyAuthDemo.Core.Filters +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class ApiKeyAuthorizeAttribute() : Attribute, IAuthorizationFilter + { + public void OnAuthorization(AuthorizationFilterContext context) + { + // Get the API key from the request headers + string? apiKeyValue = context.HttpContext.Request.Headers[Constants.API_KEY_HEADER_NAME]; + + // Get the API key from the configuration + IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService(); + string? apiKey = configuration.GetValue("ApiKey"); + + // Check if the API key is valid and set + if (apiKeyValue == null || apiKeyValue != apiKey) + { + context.Result = new UnauthorizedResult(); + } + } + } +} +``` + +::: + +## Usage + +See below for example usage (on the second GET method): + +::: code-group + +```csharp[WeatherForecastController.cs] +using ApiKeyAuthDemo.Core.Filters; +using Microsoft.AspNetCore.Mvc; + +namespace ApiKeyAuthDemo.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + + [ApiKeyAuthorize] + [HttpGet("auth")] + public IEnumerable GetAuth() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} +``` + +::: \ No newline at end of file diff --git a/docs/dotnet/index.md b/docs/dotnet/index.md index cc05f3e..ca8b144 100644 --- a/docs/dotnet/index.md +++ b/docs/dotnet/index.md @@ -11,4 +11,5 @@ #### [JWT Authentication with Cookie](./jwt-authentication-cookie.md) #### [Google Sign in Without Identity](./google-sign-in-without-identity.md) #### [Service Testing](./service-testing.md) -#### [Controller Testing](./controller-testing.md) \ No newline at end of file +#### [Controller Testing](./controller-testing.md) +#### [API Key Authentication](./api-key-auth.md) \ No newline at end of file -- 2.47.2