From c215f9060c9fed0ca9ac469f3aca807057e4f137 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:07:59 +0100 Subject: [PATCH] docs: publish v2.0 feature docs --- .../versioned_docs/version-v2.0.0/01-index.md | 26 ++ ...cation-with-azure-key-vault-integration.md | 159 +++++++ .../correlation-azure-functions.md | 154 +++++++ .../03-Features/correlation-hierarchical.md | 293 ++++++++++++ .../version-v2.0.0/03-Features/correlation.md | 149 +++++++ .../hosting/formatting-azure-functions.md | 86 ++++ .../03-Features/hosting/formatting.md | 32 ++ .../03-Features/logging-azure-functions.md | 167 +++++++ .../version-v2.0.0/03-Features/logging.md | 420 ++++++++++++++++++ .../openapi/security-definitions.md | 113 +++++ .../03-Features/security/auth/certificate.md | 156 +++++++ .../03-Features/security/auth/jwt.md | 166 +++++++ .../security/auth/shared-access-key.md | 179 ++++++++ .../version-v2.0.0-sidebars.json | 8 + docs/versions.json | 1 + 15 files changed, 2109 insertions(+) create mode 100644 docs/versioned_docs/version-v2.0.0/01-index.md create mode 100644 docs/versioned_docs/version-v2.0.0/02-Guides/shared-access-key-authentication-with-azure-key-vault-integration.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/correlation-azure-functions.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/correlation-hierarchical.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/correlation.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting-azure-functions.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/logging-azure-functions.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/logging.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/openapi/security-definitions.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/security/auth/certificate.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/security/auth/jwt.md create mode 100644 docs/versioned_docs/version-v2.0.0/03-Features/security/auth/shared-access-key.md create mode 100644 docs/versioned_sidebars/version-v2.0.0-sidebars.json diff --git a/docs/versioned_docs/version-v2.0.0/01-index.md b/docs/versioned_docs/version-v2.0.0/01-index.md new file mode 100644 index 00000000..fc56f2c0 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/01-index.md @@ -0,0 +1,26 @@ +--- +title: "Arcus - Web API" +layout: default +slug: / +sidebar_label: Welcome +sidebar_position: 1 +--- + +# Introduction + +The Arcus Web API library provides an easy way to register and configure complex functionality to your application. We provide ways to add shared access key authentication, HTTP correlation, request tracking, strict JSON formatting, and other boilerplate code that can be added with zero effort. It builds on top of [Arcus Observability](https://observability.arcus-azure.net/) to provide its HTTP correlation and request tracking. All functionality described here is also available in our [project templates](https://templates.arcus-azure.net/). + +# Installation + +The Arcus.WebApi library can be installed via NuGet, for instance: + +```shell +PM > Install-Package Arcus.WebApi.Security +``` + +For more granular packages we recommend reading the documentation. + +# License +This is licensed under The MIT License (MIT). Which means that you can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the web application. But you always need to state that Codit is the original author of this web application. + +*[Full license here](https://github.com/arcus-azure/arcus.webapi/blob/master/LICENSE)* diff --git a/docs/versioned_docs/version-v2.0.0/02-Guides/shared-access-key-authentication-with-azure-key-vault-integration.md b/docs/versioned_docs/version-v2.0.0/02-Guides/shared-access-key-authentication-with-azure-key-vault-integration.md new file mode 100644 index 00000000..0f0e7441 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/02-Guides/shared-access-key-authentication-with-azure-key-vault-integration.md @@ -0,0 +1,159 @@ +# Shared access key authentication with Azure Key Vault integration +The Arcus shared access key authentication is an API security filter to easily and safely share the same access key across a set of API endpoints. This kind of authentication is very common and therefore a popular feature in the Arcus WebApi library. + +The API security filter makes use of the [Arcus secret store](https://security.arcus-azure.net/features/secret-store) to retrieve the access keys. + +This user guide will walk through the process of adding shared access key authentication to an existing Web API application, using Azure Key Vault to store the access keys. + +## Terminology +To fully understand this user guide, some terminology is required: +* **Shared access key**: a single secret that's being used as the authentication mechanism of many API endpoints. Using a single secret means that there's also a single authorization level. +* **Arcus secret store**: central place where the application retrieve its secrets ([more info](https://security.arcus-azure.net/features/secret-store)). + +## Sample application +In this user guide, we will use a fictive API application to which we'll add shared access key authentication. We will be working with two major parts. + +The initial place where the application will be started: +```csharp +public class Program +{ + public static void Main(string[] args) + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.Services.AddRouting(); + builder.Services.AddControllers(); + + WebApplication app = builder.Build(); + app.UseRouting(); + app.Run(); + } +} +``` + +And the API controller that should be secured: +```csharp +[ApiController] +[Route("api/v1/order")] +public class OrderController : ControllerBase +{ + [HttpPost] + public IActionResult Post([FromBody] Order order) + { + // Process order... + return Accepted(); + } +} +``` + +## 1. Installation +To make use of the shared access key authentication, storing its secrets in Azure Key Vault, the following Arcus packages have to be installed. +```shell +PM > Install-Package -Name Arcus.WebApi.Security +PM > Install-Package -Name Arcus.Security.Providers.AzureKeyVault +``` + +## 2. Use Arcus secret store with Azure Key Vault integration +Once the packages are installed, add the secret store via extensions to the API application: +* 2.1 Use the `.ConfigureSecretStore` to setup the secret store with necessary secret providers +* 2.2 Use the `.AddAzureKeyVaultWithManagedIdentity` to add the Azure Key Vault secret provider that will access the secret store + +```csharp +using Arcus.Security.Core.Caching.Configuration; + +public class Program +{ + public static void Main(string[] args) + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.AddRouting(); + builder.AddControllers(); + + builder.Host.ConfigureSecretStore((config, stores) => + { + stores.AddAzureKeyVaultWithManagedIdentity("https://your-key.vault.azure.net", CacheConfiguration.Default); + }); + + WebApplication app = builder.Build(); + app.UseRouting(); + app.Run(); + } +} +``` + +## 3. Use Arcus shared access key authentication API filter +This user guide will make use of the recommended way of securing API endpoints. This is done by registering the authentication mechanism in the startup code and on the API endpoint itself. That being said, we do support finer-grained authentication. See [our dedicated feature documentation](https://webapi.arcus-azure.net/features/security/auth/shared-access-key) for more information. + +The `Arcus.WebApi.Security` package provides all the available authentication and authorization security mechanisms. For shared access key authentication, we will be using the `AddSharedAccessKeyAuthenticationFilterOnHeader` extension which will register a global API authentication security filter that applies on all available API endpoints: +```csharp +using Arcus.Security.Core.Caching.Configuration; + +public class Program +{ + public static void Main(string[] args) + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.AddRouting(); + builder.AddControllers(options => + { + options.AddSharedAccessKeyAuthenticationFilterOnHeader( + "X-API-Key", + "MyAccessKey_SecretName_AvailableInSecretStore"); + }); + + builder.Host.ConfigureSecretStore((config, stores) => + { + stores.AddAzureKeyVaultWithManagedIdentity("https://your-key.vault.azure.net", CacheConfiguration.Default); + }); + + WebApplication app = builder.Build(); + app.UseRouting(); + app.UseEndpoints(endpoints => endpoints.MapControllers()); + app.Run(); + } +} +``` + +* The `X-API-Key` is the HTTP request header name where the shared access key should be located. Missing or invalid header values will result in `401 Unauthorized` HTTP responses. +* The `MyAccessKey_SecretName_AvailableInSecretStore` is the name of the secret that holds the shared access key in the Arcus secret store. The secret store will try to find the secret by this name in one of its secret providers, in this case Azure Key Vault. + +The shared access key API authentication filter will then try to match the found access key secret with the incoming access key secret, located in the HTTP request header. Successful matches will delegate the request further up the application, unsuccessful matches will result in unauthorized results: +```powershell +$headers = @{ + 'Content-Type'='application/json' + 'X-API-Key'='invalid-key' +} + +curl -Method POST ` + -Headers $headers ` + 'http://localhost:787/api/v1/order' ` + -Body '{ "OrderId": "3", "ProductName": "Fancy desk" }' + +# Content: Shared access key in request doesn't match expected access key +# StatusCode : 401 + +$headers = @{ + 'Content-Type'='application/json' + 'X-API-Key'='valid-key' +} + +curl -Method POST ` + -Headers $headers ` + 'http://localhost:787/api/v1/order' ` + -Body '{ "OrderId": "3", "ProductName": "Fancy desk" }' + +# StatusCode : 202 +``` + +## Conclusion +In this user guide, you've seen how the Arcus shared access key API authentication filter can be added to an existing application, using the Arcus secret store that places the access key in Azure Key Vault. + +Besides shared access key authentication, we support several other mechanisms and useful API functionality. See our [feature documentation](https://webapi.arcus-azure.net/) for more information. + +## Further reading +* [Arcus Web API documentation](https://webapi.arcus-azure.net/) + * [Shared access key authentication](https://webapi.arcus-azure.net/features/security/auth/shared-access-key) +* [Arcus Security secret store documentation](https://security.arcus-azure.net/features/secret-store) + * [Azure Key Vault integration](https://security.arcus-azure.net/features/secret-store/provider/key-vault) +* [Arcus Web API project template](https://templates.arcus-azure.net/features/web-api-template) +* [Integrating Arcus API Security Filters within F# Giraffe Function Pipelines](https://www.codit.eu/blog/arcus-api-security-filters-giraffe-function-pipelines/) +* [Out-of-the-box Request Tracking, Simplified HTTP Correlation & JWT Authorization in Arcus Web API v1.0](https://www.codit.eu/blog/out-of-the-box-request-tracking-simplified-http-correlation-jwt-authorization-in-arcus-web-api-v1-0/) \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/correlation-azure-functions.md b/docs/versioned_docs/version-v2.0.0/03-Features/correlation-azure-functions.md new file mode 100644 index 00000000..7b186e9d --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/correlation-azure-functions.md @@ -0,0 +1,154 @@ +--- +title: "Correlate between HTTP requests/responses in Azure Functions" +layout: default +--- + +# Correlate between HTTP requests/responses in Azure Functions + +The `Arcus.WebApi.Logging.AzureFunctions` library provides a way to add correlation between HTTP requests for Azure Functions. + +## How This Works + +See [the general HTTP correlation page](correlation.md) to get a grasp on how HTTP correlation works. + +🚩 By default, the W3C Trace-Context specification is used as the default HTTP correlation format in Arcus, but you can go back to the (deprecated) Hierarchical system we had before, by passing `HttpCorrelationFormat.Hierarchical` to the `services.AddHttpCorrelation()`. + +## Installation + +For this feature, the following package needs to be installed: + +```shell +PM > Install-Package Arcus.WebApi.Logging.AzureFunctions +``` + +## Usage for in-process Azure Functions (receiving side) + +To make sure the correlation is added to the HTTP response, following additions have to be made in the `.Configure` methods of the function's startup: + +```csharp +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +[assembly: FunctionsStartup(typeof(Startup))] + +namespace MyHttpAzureFunction +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + builder.AddHttpCorrelation(); + } + } +} +``` + +When this addition is added, you can use the `HttpCorrelation` inside your function to call the correlation functionality and use the `IHttpCorrelationInfoAccessor` (like before) to have access to the `CorrelationInfo` of the HTTP request. +This is how you use the W3C HTTP correlation in your application: + +```csharp +using Arcus.WebApi.Logging.Correlation; + +public class MyHttpFunction +{ + private readonly AzureFunctionsInProcessHttpCorrelation _httpCorrelation; + + public MyHttpFunction(AzureFunctionsInProcessHttpCorrelation httpCorrelation) + { + _httpCorrelation = httpCorrelation; + } + + [FunctionName("HTTP-Correlation-Example")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request"); + + // Easily set the correlation information to the response if you want to expose it. + _httpCorrelation.AddCorrelationResponseHeaders(req.HttpContext); + + // Easily access correlation information in your application. + CorrelationInfo correlationInfo = _httpCorrelation.GetCorrelationInfo(); + return new OkObjectResult("This HTTP triggered function executed successfully."); + } +} +``` + +To use the old Hierarchical HTTP correlation, use the following: +```csharp +using Arcus.WebApi.Logging.Correlation; + +public class MyHttpFunction +{ + private readonly HttpCorrelation _httpCorrelation; + + public MyHttpFunction(HttpCorrelation httpCorrelation) + { + _httpCorrelation = httpCorrelation; + } + + [FunctionName("HTTP-Correlation-Example")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request"); + + if (!_httpCorrelation.TryHttpCorrelate(out string errorMessage)) + { + return new BadRequestObjectResult(errorMessage); + } + + // Easily access correlation information in your application. + CorrelationInfo correlationInfo = _httpCorrelation.GetCorrelationInfo(); + return new OkObjectResult("This HTTP triggered function executed successfully."); + } +} +``` + +> Note that the `HttpCorrelation` already has the registered `IHttpCorrelationInfoAccessor` embedded but nothing stops you from injecting the `IHtpCorrelationInfoAccessor` yourself and use that one. Behind the scenes, both instances will be the same. + +## Usage for isolated Azure Functions (receiving side) + +To make sure the correlation is added to the HTTP response, following middleware has to be added in the `Program.cs` file: +```csharp +using Microsoft.Extensions.Hosting; + +IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.UseFunctionContext() + .UseHttpCorrelation(); + }) + .Build(); + +host.Run(); +``` + +* The `UseFunctionContext` middleware makes sure that the `FunctionContext` in the Azure Function is accessible via the `IFunctionContextAccessor` function. This is required since the `IHttpCorrelationInfoAccessor` uses the `FunctionContext` to assign the correlation model. +* The `UseHttpCorrelation` middleware adds the HTTP correlation functionality to the request pipeline. This makes sure that the incoming requests results in a correlation model (accessible via the `IHttpCorrelationInfoAccessor`) and the outgoing response is enriched with this correlation model. + +The HTTP trigger function can access the `IHttpCorrelationInfoAccessor` but doesn't require any additional changes to make the HTTP correlation work (unlike the in-process Azure Functions variant). + +```csharp +public class HttpTriggerFunction +{ + private readonly IHttpCorrelationInfoAccessor _correlationAccessor; + + public HttpTriggerFunction(IHttpCorrelationInfoAccessor correlationAccessor) + { + _correlationAccessor = correlationAccessor; + } + + [Function("http")] + public HttpResponseData Run( + [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData request) + { + CorrelationInfo correlationInfo = _correlationAccessor.GetCorrelationInfo(); + + HttpResponseData response = request.CreateResponse(HttpStatusCode.OK); + return response; + } +} +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/correlation-hierarchical.md b/docs/versioned_docs/version-v2.0.0/03-Features/correlation-hierarchical.md new file mode 100644 index 00000000..3b9b0e15 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/correlation-hierarchical.md @@ -0,0 +1,293 @@ +--- +title: "Correlate between HTTP requests/responses (Hierarchical) via ASP.NET Core middleware" +layout: default +--- + +# Correlation Between HTTP Requests (Hierarchical) + +The `Arcus.WebApi.Logging` library provides a way to add correlation between HTTP requests. + +⚡ This page describes the (deprecated) Hierarchical HTTP correlation, see [this page](./correlation.md) for information on the W3C HTTP correlation. + +## How This Works + +This diagram shows an example of a user interacting with service A that calls another service B. + +![HTTP correlation diagram](/img/http-correlation-hierarchical.png) + +Three kind of correlation ID's are used to create the relationship: +* **Transaction ID**: this ID is the one constant in the diagram. This ID is used to describe the entire transaction, from begin to end. All telemetry will be tracked under this ID. +* **Operation ID**: this ID describes a single operation within the transaction. This ID is used within a service to link all telemetry correctly together. +* **Operation Parent ID**: this ID is create the parent/child link across services. When service A calls service B, then service A is the so called 'parent' of service B. + +The following list shows each step in the diagram: +1. The initial call in this example doesn't contain any correlation headers. This can be seen as a first interaction call to a service. +2. Upon receiving at service A, the application will generate new correlation information. This correlation info will be used when telemetry is tracked on the service. +3. When a call is made to service B, the **transaction ID** is sent but also the **operation parent ID** in the form of a hierarchical structure. +4. The `jkl` part of this ID, describes the new parent ID for service B (when service B calls service C, then it will use `jkl` as parent ID) +5. Service B responds to service A with the same information as the call to service B. +6. The user receives both the **transaction ID** and **operation ID** in their final response. + +💡 This correlation is based on the `RequestId` and `X-Transaction-ID` HTTP request/response headers, however, you can fully configure different headers in case you need to. +💡 The `X-Transaction-ID` can be overridden by the request, meaning: if the HTTP request already contains a `X-Transaction-ID` header, the same header+value will be used in the HTTP response. + +Additional [configuration](#configuration) is available to tweak this functionality. + +## Installation + +This feature requires to install our NuGet package: + +```shell +PM > Install-Package Arcus.WebApi.Logging +``` + +## Usage + +To fully benefit from the Arcus' HTTP correlation functionality, both sending and receiving HTTP endpoints should be configured. + +### Sending side + +To make sure the correlation is added to the HTTP request, following additions have to be made: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplication builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpCorrelation(options => options.Format = HttpCorrelationFormat.Hierarchical); +builder.Services.AddHttpClient("from-service-a-to-service-b") + .WithHttpCorrelationTracking(); + +WebApplication app = builder.Build(); +``` + +Then, the [created `HttpClient`](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) will be enhanced with HTTP correlation tracking. This means that the request will be altered to include the HTTP correlation information and the endpoint will be tracked as a [HTTP dependency telemetry](https://observability.arcus-azure.net/Features/writing-different-telemetry-types#measuring-http-dependencies). + +Alternatively, an existing `HttpClient` can be used to send a correlated HTTP request: + +```csharp +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using Arcus.WebApi.Logging.Core.Correlation; + +var client = new HttpClient(); + +IHttpCorrelationInfoAccessor accessor = ... // From dependency injection. +ILogger logger = ... // From dependency injection. + +var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/service-b"); +await client.SendAsync(request, accessor, logger); +``` + +💡 The HTTP correlation tracking can also be configured, see [this section](#configuring-http-correlation-client-tracking) for more information. + +### Receiving side + +To make sure the correlation is added to the HTTP response, following additions have to be made: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpCorrelation(options => options.Format = HttpCorrelationFormat.Hierarchical); + +WebApplication app = builder.Build(); +app.UseHttpCorrelation(); +app.UseRouting(); +app.UseRequestTracking(); +``` + +> ⚠ Because the correlation is based on ASP.NET Core middleware, it's recommended to place it before the `.UseRouting` call. + +> ⚡ To use HTTP correlation in Azure Functions, see [this dedicated page](correlation-azure-functions.md), as the configuration on the receiving is slightly different. + +The `UseRequestTracking` extension will make sure that the incoming HTTP request will be tracked as a 'request' in Application Insights (if configured). +For more information on HTTP request tracking, see [our dedicated feature documentation page](./logging.md); + +## Configuration + +The HTTP correlation can be configured with different options to work for your needs. + +### Configuring HTTP correlation services + +The HTTP correlation is available throughout the application via the registered `IHttpCorrelationInfoAccessor`. This HTTP correlation accessor is both used in sending/receiving functionality. +Some extra options are available to alter the functionality of the correlation: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpCorrelation(options => +{ + // ⚠ Use hierarchical format to make use of these options. + options.Format = HttpCorrelationFormat.Hierarchical; + + // Configuration on the transaction ID (`X-Transaction-ID`) request/response header. + // --------------------------------------------------------------------------------- + + // Whether the transaction ID can be specified in the request, and will be used throughout the request handling. + // The request will return early when the `.AllowInRequest` is set to `false` and the request does contain the header (default: true). + options.Transaction.AllowInRequest = true; + + // Whether or not the transaction ID should be generated when there isn't any transaction ID found in the request. + // When the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header, no value will be available for the transaction ID; + // otherwise a GUID will be generated (default: true). + options.Transaction.GenerateWhenNotSpecified = true; + + // Whether to include the transaction ID in the response (default: true). + options.Transaction.IncludeInResponse = true; + + // The header to look for in the HTTP request, and will be set in the HTTP response (default: X-Transaction-ID). + options.Transaction.HeaderName = "X-Transaction-ID"; + + // The function that will generate the transaction ID, when the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header. + // (default: new `Guid`). + options.Transaction.GenerateId = () => $"Transaction-{Guid.NewGuid()}"; + + // Configuration on the operation ID (`RequestId`) response header. + // ---------------------------------------------------------------- + + // Whether to include the operation ID in the response (default: true). + options.Operation.IncludeInResponse = true; + + // The header that will contain the operation ID in the HTTP response (default: RequestId). + options.Operation.HeaderName = "RequestId"; + + // The function that will generate the operation ID header value. + // (default: new `Guid`). + options.Operation.GenerateId = () => $"Operation-{Guid.NewGuid()}"; + + // Configuration on operation parent ID request header (`Request-Id`). + // ------------------------------------------------------------------ + + // Whether to extract the operation parent ID from the incoming request following W3C Trace-Context standard (default: true). + // More information on operation ID and operation parent ID, see [this documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation). + options.UpstreamService.ExtractFromRequest = false; + + // The header that will contain the operation parent ID in the HTTP request (default: Request-Id). + options.UpstreamService.HeaderName = "x-request-id"; + + // The function that will generate the operation parent ID when it shouldn't be extracted from the request. + options.UpstreamService.GenerateId = () => $"Parent-{Guid.newGuid()}"; +}); +``` + +### Configuring HTTP correlation client tracking + +When sending tracked HTTP requests, some options can be configured to customize the tracking to your needs. + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpClient("from-service-a-to-service-b") + .WithHttpCorrelationTracking(options => + { + // The header that will be used to set the HTTP correlation transaction ID. (Default: X-Transaction-ID) + options.TransactionIdHeaderName = "X-MyTransaction-Id"; + + // The header that will be used to set the upstream service correlation ID. (Default: Request-Id) + options.UpstreamServiceHeaderName = "X-MyRequest-Id"; + + // The function to generate the dependency ID for the called service. + // (service A tracks dependency with this ID, service B tracks request with this ID). + options.GenerateDependencyId = () => $"my-request-id-{Guid.NewGuid()}"; + + // The dictionary containing any additional contextual inforamtion that will be used when tracking the HTTP dependency (Default: empty dictionary). + options.AddTelemetryContext(new Dictionary + { + ["My-HTTP-custom-key"] = "Any additional information" + }); + }); +``` + +The same options can be configured when sending correlated HTTP requests with an existing `HttpClient`: + +```csharp +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using Arcus.WebApi.Logging.Core.Correlation; + +var client = new HttpClient(); + +IHttpCorrelationInfoAccessor accessor = ... // From dependency injection. +ILogger logger = ... // From dependency injection. + +var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/service-b"); +await client.SendAsync(request, accessor, logger, options => +{ + // The header that will be used to set the HTTP correlation transaction ID. (Default: X-Transaction-ID) + options.TransactionIdHeaderName = "X-MyTransaction-Id"; + + // The header that will be used to set the upstream service correlation ID. (Default: Request-Id) + options.UpstreamServiceHeaderName = "X-MyRequest-Id"; + + // The function to generate the dependency ID for the called service. + // (service A tracks dependency with this ID, service B tracks request with this ID). + options.GenerateDependencyId = () => $"my-request-id-{Guid.NewGuid()}"; +}); +``` + +## Dependency injection + +To use the HTTP correlation in your application code, you can use a dedicated marker interface called `IHttpCorrelationInfoAccessor`. +This will help you with accessing and setting the HTTP correlation. + +Note that the correlation is a scoped dependency, so will be the same instance across the HTTP request. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Arcus.WebApi.Logging.Core.Correlation; + +[ApiController] +[Route("api/v1/order")] +public class OrderController : ControllerBase +{ + private readonly IHttpCorrelationInfoAccessor _accessor; + + public OrderController(IHttpCorrelationInfoAccessor accessor) + { + _accessor = accessor; + } + + [HttpPost] + public IActionResult Post([FromBody] Order order) + { + CorrelationInfo correlation = _accessor.GetCorrelationInfo(); + + _accessor.SetCorrelationInfo(correlation); + } +} +``` + +## Logging + +As an additional feature, we provide an extension to use the HTTP correlation directly in a [Serilog](https://serilog.net/) configuration as an [enricher](https://github.com/serilog/serilog/wiki/Enrichment). +This adds the correlation information of the current request to the log event as a log property called `TransactionId`, `OperationId`, and `OperationParentId`. + +**Example** + +- `TransactionId`: `A5E90591-ADB0-4A56-818A-AC5C02FBFF5F` +- `OperationId`: `79BB196A-B0CC-4F5C-B48A-AB87850346AF` +- `OperationParentId`: `0BC101AC-5E41-43B5-B020-3EF467629E3D` + +**Usage** +The enricher requires access to the application services so it can get the correlation information. + +```csharp +using Microsoft.AspNetCore.Builder; +using Serilog; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +builder.Host.UseSerilog((context, serviceProvider, config) => +{ + return new LoggerConfiguration() + .Enrich.WithHttpCorrelationInfo(serviceProvider) + .WriteTo.Console() + .CreateLogger(); +}); + +WebApplication app = builder.Build(); +app.UseHttpCorrelation(); +app.UseRouting(); +app.UseRequestTracking(); +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/correlation.md b/docs/versioned_docs/version-v2.0.0/03-Features/correlation.md new file mode 100644 index 00000000..7f10329c --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/correlation.md @@ -0,0 +1,149 @@ +--- +title: "Correlate between HTTP requests/responses (W3C) via ASP.NET Core middleware" +layout: default +--- + +# Correlation Between HTTP Requests (W3C) +The `Arcus.WebApi.Logging` library provides a way to add correlation between HTTP requests. + +🚩 This page describes [W3C Trace-Context](https://www.w3.org/TR/trace-context-1/) correlation, see [this page](./correlation-hierarchical.md) for information on the (deprecated) Hierarchical HTTP correlation. + +## How This Works +This diagram shows an example of a user interacting with service A that calls another service B. + +![HTTP correlation diagram](/img/http-correlation-w3c.png) + +Three kind of correlation ID's are used to create the relationship: +* **Transaction ID**: this ID is the one constant in the diagram. This ID is used to describe the entire transaction, from begin to end. All telemetry will be tracked under this ID. +* **Operation ID**: this ID describes a single operation within the transaction. This ID is used within a service to link all telemetry correctly together. +* **Operation Parent ID**: this ID is create the parent/child link across services. When service A calls service B, then service A is the so called 'parent' of service B. + +The following list shows each step in the diagram: +1. The initial call in this example doesn't contain any correlation headers. This can be seen as a first interaction call to a service. +2. Upon receiving at service A, the application will generate new correlation information. This correlation info will be used when telemetry is tracked on the service. +3. When a call is made to service B, the **transaction ID** is sent but also the **operation parent ID** in the form of a W3C structure: `00-transactionId-parentId-00`. +4. The `def` part of this ID, describes the new parent ID for service B (when service B calls service C, then it will use a different parent ID) +5. Service B responds to service A with the same information as the call to service B. +6. The user receives both the **transaction ID** and **operation ID** in their final response. + +💡 This correlation is based on the `traceparent` HTTP request/response header. + +Additional [configuration](#configuration) is available to tweak this functionality. + +### ⚡ Automatic dependency tracking +When choosing for the W3C HTTP correlation format, Arcus and Microsoft technology works seemingly together. When a HTTP request is received on a service that uses the Arcus W3C HTTP correlation, all remote dependencies managed my Microsoft (HTTP, ServiceBus, EventHubs...) are tracked automatically, without additional configuration. + +## Installation +This feature requires to install our NuGet package: + +```shell +PM > Install-Package Arcus.WebApi.Logging +``` + +## Usage +To fully benefit from the Arcus' HTTP correlation functionality, both sending and receiving HTTP endpoints should be configured. +These three things need to be added on both sides: +1. Adding HTTP correlation to application services with `services.AddHttpCorrelation()`. +2. Adding HTTP correlation to application middleware with `app.UseHttpCorrelation()` and `app.UseRequestTracking()` ([more info on request tracking](logging.md)). +3. Adding HTTP correlation enricher to Serilog configuration with `Enrich.WithHttpCorrelationInfo(app.Services)` ([more info](#logging)). + +```csharp +using Microsoft.AspNetCore.Builder; +using Serilog; +using Serilog.Configuration; + +WebApplication builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpCorrelation(); +builder.Services.AddHttpClient("from-service-a-to-service-b"); + +builder.Host.UseSerilog((context, serviceProvider, config) => +{ + config.Enrich.WithHttpCorrelationInfo(serviceProvider) + .WriteTo.Console() + .CreateLogger(); +}); + +WebApplication app = builder.Build(); +app.UseHttpCorrelation(); + +// ⚠ Because the correlation is based on ASP.NET Core middleware, it's recommended to place it before the `.UseRouting` call. +app.UseRouting(); +app.UseRequestTracking(); +``` + +> ⚡ The `UseRequestTracking` extension will make sure that the incoming HTTP request will be tracked as a 'request' in Application Insights (if configured). +> For more information on HTTP request tracking, see [our dedicated feature documentation page](./logging.md); + +> ⚡ As an additional feature, we provide an extension to use the HTTP correlation directly in a [Serilog](https://serilog.net/) configuration as an [enricher](https://github.com/serilog/serilog/wiki/Enrichment). +> This adds the correlation information of the current request to the log event as a log property called `TransactionId`, `OperationId`, and `OperationParentId`. +> **Example** +> - `TransactionId`: `4b1c0c8d608f57db7bd0b13c88ef865e` +> - `OperationId`: `4a3c1c8d` +> - `OperationParentId`: `4c6893cc6c6cad10` + + +> ⚡ To use HTTP correlation in Azure Functions, see [this dedicated page](correlation-azure-functions.md), as the configuration on the receiving side is slightly different. + +## Configuration +The HTTP correlation can be configured with different options to work for your needs. + +### Configuring HTTP correlation services +The HTTP correlation is available throughout the application via the registered `IHttpCorrelationInfoAccessor`. This HTTP correlation accessor is both used in sending/receiving functionality. +Some extra options are available to alter the functionality of the correlation: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +builder.Services.AddHttpCorrelation(options => +{ + // Configuration on the transaction ID (`X-Transaction-ID`) response header. + // --------------------------------------------------------------------------------- + + // Whether to include the transaction ID in the response (default: true). + options.Transaction.IncludeInResponse = true; + + // The header will be set in the HTTP response (default: X-Transaction-ID). + options.Transaction.HeaderName = "X-Transaction-ID"; + + // Configuration on the operation ID (`X-Operation-Id`) response header. + // ---------------------------------------------------------------- + + // Whether to include the operation ID in the response (default: true). + options.Operation.IncludeInResponse = true; + + // The header that will contain the operation ID in the HTTP response (default: X-Operation-Id). + options.Operation.HeaderName = "X-MyOperation-Id"; +}); +``` + +## Dependency injection +To use the HTTP correlation in your application code, you can use a dedicated marker interface called `IHttpCorrelationInfoAccessor`. +This will help you with accessing and setting the HTTP correlation. + +Note that the correlation is a scoped dependency, so will be the same instance across the HTTP request. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Arcus.WebApi.Logging.Core.Correlation; + +[ApiController] +[Route("api/v1/order")] +public class OrderController : ControllerBase +{ + private readonly IHttpCorrelationInfoAccessor _accessor; + + public OrderController(IHttpCorrelationInfoAccessor accessor) + { + _accessor = accessor; + } + + [HttpPost] + public IActionResult Post([FromBody] Order order) + { + CorrelationInfo correlation = _accessor.GetCorrelationInfo(); + + _accessor.SetCorrelationInfo(correlation); + } +} +``` diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting-azure-functions.md b/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting-azure-functions.md new file mode 100644 index 00000000..8fda4a89 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting-azure-functions.md @@ -0,0 +1,86 @@ +--- +title: "JSON formatting for Azure Functions (isolated)" +layout: default +--- + +# JSON formatting +The `Arcus.WebApi.Hosting.AzureFunctions` library provides a way to restrict and configure the JSON input and output formatting of your application. +This allows for an easier and more secure formatting when working with JSON types. + +> ⚠ These features are only available for Azure Functions using the isolated functions worker. For more information on the difference between in-process and isolated Azure Functions, see [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide). + +## Installation +These features require to install our NuGet package: + +```shell +PM > Install-Package Arcus.WebApi.Hosting.AzureFunctions +``` + +## Restricting JSON format +We have provided an extension that will allow you to restrict your input and output formatting to only JSON formatting (Only the `SystemTextJsonInputFormatter` will remain). +This means that all other incoming content will result in `UnsupportedMediaType` failures and outgoing content will fail to serialize back to the sender. With this functionality, you'll be sure that you only have to deal with JSON. + +❗ Make sure that the `Content-Type` and `Allow` are set to `application/json`. + +Following example shows you how you can configure this: + +```csharp +using Microsoft.Extensions.Hosting; + + IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.UseOnlyJsonFormatting(); + }) + .Build(); +``` + +## Configure JSON format +We have provided an extension that will allow you to configure a [`JsonObjectSerializer`](https://learn.microsoft.com/en-us/dotnet/api/azure.core.serialization.jsonobjectserializer?view=azure-dotnet) which will be registered in the application services. This JSON serializer can be injected in your Azure Function implementation so that incoming and outgoing models are handled the same way, across multiple functions. +This makes the JSON formatting more centralized and easier to maintain. + +Following example shows you how you can configure these options: + +```csharp +using System.Text.Json; +using Microsoft.Extensions.Hosting; + +IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.ConfigureJsonFormatting(options => + { + options.Converters.Add(new JsonStringEnumConverter()); + }); + }) + .Build(); +``` + +After that, the `JsonObjectSerializer` can be injected in your function for deserialization/serialization of incoming/outgoing models: + +```csharp +using Azure.Core.Serialization; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +public class OrderFunction +{ + private readonly JsonObjectSerializer _jsonSerializer; + + public OrderFunction(JsonObjectSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + [Function("order-processing")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData request) + { + var order = await request.ReadFromJsonAsync(_jsonSerializer); + + HttpResponseData response = request.CreateResponse(); + await response.WriteAsJsonAsync(order, _jsonSerializer); + return response; + } +} +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting.md b/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting.md new file mode 100644 index 00000000..892dce67 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/hosting/formatting.md @@ -0,0 +1,32 @@ +--- +title: "JSON formatting" +layout: default +--- + +# JSON formatting +The `Arcus.WebApi.Hosting` library provides a way to restrict and configure the JSON input and output formatting of your application. +This allows for a easier and more secure formatting when working with JSON types. + +## Installation +These features require to install our NuGet package: + +```shell +PM > Install-Package Arcus.WebApi.Hosting +``` + +## Restricting JSON format +We have provided an extension that will allow you to restrict your input and output formatting to only JSON formatting (Only the `SystemTextJsonInputFormatter` will remain). This means that all other incoming content will result in `UnsupportedMediaType` failures and outgoing content will fail to serialize back to the sender. With this functionality, you'll be sure that you only have to deal with JSON. + +Following example shows you how you can configure this: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddControllers(mvcOptions => +{ + mvcOptions.OnlyAllowJsonFormatting(); +}); +``` diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/logging-azure-functions.md b/docs/versioned_docs/version-v2.0.0/03-Features/logging-azure-functions.md new file mode 100644 index 00000000..d9fd57f5 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/logging-azure-functions.md @@ -0,0 +1,167 @@ +--- +title: "Logging in Azure Functions (isolated)" +layout: default +--- + +# Logging in Azure Functions (isolated) +The `Arcus.WebApi.Logging.AzureFunctions` package provides a way to log several kinds of information during the receival and handling of HTTP requests. + +To send the logging information to Application Insights, see [this user guide](https://observability.arcus-azure.net/Guidance/use-with-dotnet-and-functions). + +> ⚠ The functionality explained here is only available for Azure Functions that are isolated. It is not available for Azure Functions that are running in-process. For more information on the difference, see [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide). + +## Installation +These features require to install our NuGet package + +```shell +PM > Install-Package Arcus.WebApi.Logging.AzureFunctions +``` + +## Logging unhandled exceptions +The `AzureFunctionsExceptionHandlingMiddleware` class can be added to the Azure Functions worker pipeline to log unhandled exceptions that are thrown during request processing. +The unhandled exceptions are caught by this middleware component and are logged through the `ILogger` implementations that are configured inside the project. + +> ⚡ The HTTP status code `500` is used by default as response code when an unhandled exception is caught. + +### Usage +To use this middleware, add the following line: +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.UseExceptionHandling(); + }) + .Build(); + + host.Run(); + } +} +``` + +### Configuration +When custom exception handling is required, you can inherit from the `AzureFunctionsExceptionHandlingMiddleware` to create your own middleware component and register it with Arcus' extension. + +This example implements the exception handling middleware to influence the log message and adds a custom determination of the HTTP response status code. +```csharp +using Arcus.WebApi.Logging.AzureFunctions; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +public class MyExceptionHandlingMiddleware : AzureFunctionsExceptionHandlingMiddleware +{ + protected override void LogException(ILogger logger, Exception exception) + { + logger.LogCritical(exception, "Custom exception handling!"); + } + + protected override HttpResponseData CreateFailureResponse(Exception exception, HttpStatusCode defaultFailureStatusCode, HttpRequestData request) + { + if (exception is ValidationException) + { + return request.CreateResponse(HttpStatusCode.BadRequest); + } + else if (exception is TimeoutException) + { + return request.CreateResponse(HttpStatusCode.ServerTimeout); + } + else + { + return request.CreateResponse(defaultFailureStatusCode); + } + } +} +``` + +Next, make sure that you pass along the exception middleware to the exception handling extension. +```csharp +.ConfigureFunctionsWorkerDefaults(builder => +{ + app.UseExceptionHandling(); +}); +``` + +## Logging incoming requests +The `AzureFunctionsRequestTrackingMiddleware` class can be added to the Azure Functions worker pipeline to log any incoming HTTP requests. +The requests are tracked with [Arcus Observability](https://observability.arcus-azure.net/Features/writing-different-telemetry-types#incoming-http-requests-in-azure-function-http-trigger) so that they will show up as requests in Application Insights when the application is using [Arcus Application Insights Serilog sink](https://observability.arcus-azure.net/Features/sinks/azure-application-insights). + +> ⚠ The HTTP request and response body are not tracked by default. + +> ⚡ The HTTP request headers are logged by default, except certain security headers are by default omitted: `Authentication`, `X-Api-Key` and `X-ARR-ClientCert`. + +Tracking a HTTP request will look like this in the logs: +`HTTP Request POST http://localhost:5000/weatherforecast completed with 200 in 00:00:00.0191554 at 03/23/2020 10:12:55 +00:00 - (Context: [Content-Type, application/json], [Body, {"today":"cloudy"}])` + +### Usage +To use this middleware component, add the following line to your startup code: +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.UseRequestTracking(); + }) + .Build(); + + host.Run(); + } +} +``` + +### Configuration +The middleware component can be configured to influence the behavior of the HTTP request tracking. This ranges from including HTTP request/response bodies, specifying which routes should be tracked, filtering HTTP request headers, and more. +To learn more about these options, see [the configuration section at the general Web API page](./logging.md) as these options are identical for Azure Functions HTTP triggers and Web API's. + +### Customization +Optionally, the middleware component can be extended even further by inheriting from the `AzureFunctionsRequestTrackingMiddleware` class. This allows full control over the sanitization process of the HTTP request/response body and HTTP request headers. + +The following example shows how a custom implementation makes sure that a specific header is not entirely excluded but is redacted from the HTTP request tracking. +```csharp +public class RedactedRequestTrackingMiddleware : AzureFunctionsRequestTrackingMiddleware +{ + public RedactedRequestTrackingMiddleware(RequestTrackingOptions options) : base(options) + { + } + + protected override IDictionary SanitizeRequestHeaders(IDictionary requestHeaders) + { + var headerName = "X-Private-Client-Id"; + if (requestHeaders.TryGetValue(headerName, out StringValues value)) + { + requestHeaders[headerName] = ""; + } + } +} +``` + +> 💡 Note that the custom middleware also has a constructor overload to pass-in additional options that allow you to customize the sanitization process in your custom middleware component. + +This custom middleware component can be registered with an `.UseRequestTracking<>()` extension overload, which allows you to configure any additional options, if required. +```csharp +using Microsoft.Extensions.Hosting; + +public class Program +{ + public static void Main(string[] args) + { + IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults(builder => + { + builder.UseRequestTracking(); + }) + .Build(); + + host.Run(); + } +} +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/logging.md b/docs/versioned_docs/version-v2.0.0/03-Features/logging.md new file mode 100644 index 00000000..c404cb51 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/logging.md @@ -0,0 +1,420 @@ +--- +title: "Logging in Web API applications" +layout: default +--- + +# Logging in Web API applications + +The `Arcus.WebApi.Logging` package provides a way to log several kinds of information during the receival and handling of HTTP requests. + +💡 To send the logging information to Application Insights, see [Arcus Observability](https://observability.arcus-azure.net/Features/sinks/azure-application-insights). + +## Installation + +These features require to install our NuGet package + +```shell +PM > Install-Package Arcus.WebApi.Logging +``` + +## Logging unhandled exceptions + +The `ExceptionHandlingMiddleware` class can be added to the ASP.NET Core pipeline to log unhandled exceptions that are thrown during request processing. +The unhandled exceptions are caught by this middleware component and are logged through the `ILogger` implementations that are configured inside the project. + +⚡ The HTTP status code `500` is used as response code when an unhandled exception is caught. +However, when the runtime throws a `BadHttpRequestException` we will reflect this by returning the corresponding status code determined by the runtime. + +### Usage + +To use this middleware, add the following line of code: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseRouting(); + +app.UseExceptionHandling(); + +app.UseEndpoints(endpoints => endpoints.MapControllers()); +``` + +If you have multiple middleware components configured, it is advised to put the `ExceptionHandlingMiddleware` as soon as possible. +By doing so, unhandled exceptions that might occur in other middleware components will also be logged by the `ExceptionHandlingMiddleware` component. + +### Configuration + +When custom exception handling is required, you can inherit from the `ExceptionHandlingMiddleware` to create your own middleware component and register it with Arcus' extension. + +This example implements the exception handling middleware to influence the log message and adds a custom determination of the HTTP response status code. + +```csharp +using Arcus.WebApi.Logging; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +public class MyExceptionHandlingMiddleware : ExceptionHandlingMiddleware +{ + public MyExceptionHandlingMiddleware(RequestDelegate next) : base(next) + { + } + + protected override void LogException(ILogger logger, Exception exception) + { + logger.LogCritical(exception, "Custom exception handling!"); + } + + protected override void WriteFailureToResponse(Exception exception, HttpStatusCode defaultFailureStatusCode, HttpContext context) + { + if (exception is ValidationException) + { + context.Response.StatusCode = (int) HttpStatusCode.BadRequest; + } + else if (exception is TimeoutException) + { + context.Response.StatusCode = (int) HttpStatusCode.ServerTimeout; + } + else + { + context.Response.StatusCode = (int) defaultFailureStatusCode; + } + } +} +``` + +Next, make sure that you pass along the exception middleware to the exception handling extension. + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseExceptionHandling(); +``` + +## Logging incoming requests + +The `RequestTrackingMiddleware` class can be added to the ASP.NET Core pipeline to log all received HTTP requests. +The incoming requests are logged by this middleware component using the `ILogger` implementations that are configured in the project. + +⚠ The HTTP request body is not logged by default. + +⚡ The HTTP request headers are logged by default, except certain security headers are by default omitted: `Authentication`, `X-Api-Key` and `X-ARR-ClientCert`. +See [configuration](#configuration) for more details. + +### Example + +`HTTP Request POST http://localhost:5000/weatherforecast completed with 200 in 00:00:00.0191554 at 03/23/2020 10:12:55 +00:00 - (Context: [Content-Type, application/json], [Body, {"today":"cloudy"}])` + +### Usage + +To use this middleware, add the following line of code in the `Startup.Configure` method: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseRouting(); + +app.UseRequestTracking(); +app.UseExceptionHandling(); + +app.UseEndpoints(endpoints => endpoints.MapControllers()); +``` + +### Configuration + +The request tracking middleware has several configuration options to manipulate what the request logging emits should contain. + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseRouting(); + +app.UseRequestTracking(options => +{ + // Whether or not the HTTP request body should be included in the request tracking logging emits. + // (default: `false`) + options.IncludeRequestBody = true; + + // Whether or not the configured HTTP request headers should be included in the request tracking logging emits. + // (default: `true`) + options.IncludeRequestHeaders = true; + + // All omitted HTTP request header names that should always be excluded from the request tracking logging emits. + // (default: `[ "Authentication", "X-Api-Key", "X-ARR-ClientCert" ]`) + options.OmittedRequestHeaderNames.Add("X-My-Secret-Header"); + + // Size of the request body buffer (in bytes) which should be tracked. Request bodies greater than this buffer will only be partly present in the telemetry. + // (default: no limit) + options.RequestBodyBufferSize = 10 * 1024 * 1024; // 10MB + + // Whether or not the HTTP response body should be included in the request tracking logging emits. + // (default: `false`) + options.IncludeResponseBody = true; + + // Size of the response body buffer (in bytes) which should be tracked. Response bodies greater than this buffer will only be partly present in the telemetry. + // (default: no limit) + options.ResponseBodyBufferSize = 10 * 1024 * 1024; // 10MB + + // All response HTTP status codes that should be tracked. If not defined, all status codes are considered included and will all be tracked. + // (default: empty list, which means all will be tracked) + // Following example will change the default behavior so that only HTTP responses with the status code 500 `InternalServerError` will be tracked. + options.TrackedStatusCodes.Add(HttpStatusCode.InternalServerError); + + // All response HTTP status code ranges that should be tracked. If not defined, all status codes are considered included and will be tracked. + // (default: empty list, which means all will be tracked) + // Following example will change the default behavior so that only HTTP response status codes in the range of 500 to 599 (inconclusive) will be tracked. + options.TrackedStatusCodeRanges.Add(new StatusCodeRange(500, 599)); + + // All omitted HTTP routes that should be excluded from the request tracking logging emits. + // (default: no routes). + options.OmittedRoutes.Add("/api/v1/health"); +}); + +app.UseEndpoints(endpoints => endpoints.MapControllers()); +``` + +### Excluding certain routes + +You can opt-out for request tracking on one or more endpoints (operation and/or controller level). +This can come in handy if you have certain endpoints with rather large request bodies or want to boost performance or are constantly probed to monitor the application. +This can easily be done by using the `ExcludeRequestTrackingAttribute` on the endpoint or controller of your choosing. + +```csharp +using Arcus.WebApi.Logging; + +[ApiController] +[ExcludeRequestTracking] +public class OrderController : ControllerBase +{ + [HttpPost] + public IActionResult BigPost() + { + Stream bigRequestBody = Request.Body; + return Ok(); + } +} +``` + +#### Excluding request/response bodies on specific routes + +When used as in the example above, then all routes of the controller will be excluded from the telemetry tracking. +The exclude attribute allows you to specify on a more specific level what part of the request should be excluded. + +```csharp +using Arcus.WebApi.Logging; + +[ApiController] +public class OrderController : ControllerBase +{ + // Only exclude the request body from the request tracking. + // The request will still be tracked and will contain the request headers and the response body (if configured). + [HttpPost] + [RequestTracking(Exclude.RequestBody)] + public IActionResult BigRequestBodyPost() + { + Stream bigRequestBody = Request.Body; + return Ok(); + } + + // Only exclude the response body from the request tracking. + // The request will still be tracked and will contain the request headers and the request body (if configured). + [HttPost] + [RequestTracking(Exclude.ResponseBody)] + public async Task BigResponseBodyPost() + { + Stream responseBody = ... + responseBody.CopyToAsync(Response.Body); + + return Ok(); + } +} +``` + +#### Including HTTP status codes/status code ranges on specific routes + +With the `RequestTracking` attribute, you can include a fixed HTTP status code or a range of HTTP status codes on a specific route. +This allows for a more finer grained control of the request tracking than to specify these status codes in the request tracking options. + +```csharp +using System.Net; +using Arcus.WebApi.Logging; + +[ApiController] +public class OrderController : ControllerBase +{ + // Only when the response returns a 500 `InternalServerError` will the request tracking occur for this endpoint. + [HttpPost] + [RequestTracking(HttpStatusCode.InternalServerError)] + public IActionResult PostThatTracksInternalServerError() + { + return Ok(); + } + + // Only when the response returns a HTTP status code in the range of 500 to 599 (inconclusive) wll the request tracking occur for this endpoint. + [HttpPost] + [RequestTracking(500, 599)] + public IActionResult PostThatTracksServerErrors() + { + return Ok(); + } +} +``` + +### Customization + +Optionally, one can inherit from this middleware component and override several default functionality: +- by default, all the request headers (except some known authentication headers) are tracked +- by default, when tracking the request body, the entire body is tracked +- by default, when tracking the response body, the entire body is tracked + +Following example shows how specific headers can be redacted without omitting them: + +```csharp +using Arcus.WebApi.Logging; + +public class EmptyButNotOmitRequestTrackingMiddleware : RequestTrackingMiddleware +{ + public EmptyButNotOmitRequestTrackingMiddleware( + RequestDelegate next, + ILogger logger) + : base(next, logger) + { + } + + protected override IDictionary SanitizeRequestHeaders(IDictionary requestHeaders) + { + requestHeaders["X-Api-Key"] = ""; + return requestHeaders; + } + + // Also available to be overridden: + // `SanitizeRequestBody` + // `SanitizeResponseBody` +} +``` + +And, configure your custom middleware like this in the `Startup.cs`: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseRouting(); + +app.UseRequestTracking(options => options.OmittedHeaderNames.Clear()); + +app.UseEndpoints(endpoints => endpoints.MapControllers()); +``` + +So the resulting log message becomes: + +`HTTP Request POST http://localhost:5000/weatherforecast completed with 200 in 00:00:00.0191554 at 03/23/2020 10:12:55 +00:00 - (Context: [X-Api-Key,])` + +#### Reducing requests to specific HTTP status code(s) + +By default, all the responded HTTP status codes will be tracked but this can be altered according to your choosing. + +Consider the following API controller. When we configure request tracking, both the `400 BadRequest` response as the `201 Created` response will be tracked. + +```csharp +[ApiController] +public class OrderController : ControllerBase +{ + [HttpPost] + public IActionResult Create([FromBody] Order order) + { + if (order.Id is null) + { + // Request tracking will happen on this response. + return BadRequest("Order ID should be filled out"); + } + + // Request tracking will happened on this response. + return Created($"/orders/{order.Id}", order); + } +} +``` + +Let's change the request tracking to only track successful '201 Created' responses. +This can be changed via the options: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); +WebApplication app = builder.Build(); + +app.UseRouting(); + +app.UseRequestTracking(options => options.TrackedStatusCodes.Add(HttpStatusCode.Created)); + +app.UseEndpoints(endpoints => endpoints.MapControllers()); +``` + +This means that every endpoint will only track `201 Created` responses. Changing this in the options is usually for when you want to streamline your entire application to only track a certain set of status codes. +More fine grained control can be achieved via placing an attribute on either the controller's class definition or the endpoint method: + +```csharp +using Arcus.WebApi.Logging; + +[ApiController] +public class OrderController : ControllerBase +{ + [HttpPost] + [RequestTracking(HttpStatusCode.Created)] + public IActionResult Create([FromBody] Order order) + { + if (order.Id is null) + { + // No request tracking will appear in the logs. + return BadRequest("Order ID should be filled out"); + } + + // Request tracking will only happen on this response. + return Created($"/orders/{order.Id}", order); + } +} +``` + +> Note that this `RequestTracking` attribute can be added multiple times and works together with the configured options. +> The end result will be the accumulated result of all the applied attributes, both on the method and on the controller, and the configured options. + +## Tracking application version + +The `Arcus.WebApi.Logging` library allows you to add application version tracking to your ASP.NET application which will include your application version to a configurable response header. + +This functionality uses the `IAppVersion`, available in the [Arcus.Observability](https://observability.arcus-azure.net/features/telemetry-enrichment#version-enricher) library, for retrieving the current application version. +Such an instance **must** be registered in order for the version tracking to work correctly. + +> ⚠ **Warning:** Only use the version tracking for non-public endpoints otherwise the version information is leaked and it can be used for unintended malicious purposes. + +Adding the version tracking can be done by the following: + +```csharp +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSingleton(); + +WebApplication app = builder.Build(); + +// Uses the previously registered `IAppVersion` service to include the application to the default `X-Version` response header. +app.UseVersionTracking(); + +// Uses the previously registered `IAppVersion` service to include the application to the custom `X-My-Version` response header. +app.UseVersionTracking(options => options.Header = "X-My-Version"); +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/openapi/security-definitions.md b/docs/versioned_docs/version-v2.0.0/03-Features/openapi/security-definitions.md new file mode 100644 index 00000000..ceec5363 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/openapi/security-definitions.md @@ -0,0 +1,113 @@ +--- +title: "Adding security information to OpenAPI documentation" +layout: default +--- + +# Adding OAuth security definition to API operations + +When an API is secured via OAuth, [Shared Access Key authentication](../security/auth/shared-access-key.md), [Certificate authentication](../security/auth/certificate.md), it is helpful if the Open API documentation makes this clear via a security scheme and the API operations that require authentication/authorization automatically inform the consumer that it is possible that a 403 Forbidden or 401 Unauthorized response is returned. + +These `IOperationFilter`'s that are part of this package exposes this functionality: +- [Adding OAuth security definition to API operations](#adding-oauth-security-definition-to-api-operations) + - [Installation](#installation) + - [Usage](#usage) + - [Certificate](#certificate) + - [OAuth](#oauth) + - [Shared Access Key](#shared-access-key) + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.WebApi.OpenApi.Extensions +``` + +## Usage + +### Certificate + +To indicate that an API is protected by [Certificate authentication](../security/auth/certificate.md), you need to add `CertificateAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckle's Swagger generation: + +```csharp +using Arcus.WebApi.OpenApi.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.OpenApi.Models; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new OpenApiInfo { Title = "My API v1", Version = "v1" }); + + string securitySchemaName = "my-certificate"; + setupAction.AddSecurityDefinition(securitySchemaName, new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey + }); + + setupAction.OperationFilter(securitySchemaName); +}); +``` + +> Note: the `CertificateAuthenticationOperationFilter` has by default `"certificate"` as `securitySchemaName`. + +### OAuth + +To indicate that an API is protected by OAuth, you need to add `OAuthAuthorizeOperationFilter` as an `IOperationFilter` when configuring Swashbuckle's Swagger generation: + +```csharp +using Arcus.WebApi.OpenApi.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.OpenApi.Models; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); + + string securitySchemaName = "my-oauth2"; + setupAction.AddSecurityDefinition(securitySchemaName, new OAuth2Scheme + { + Flow = "implicit", + AuthorizationUrl = $"{authorityUrl}connect/authorize", + Scopes = scopes + }); + + setupAction.OperationFilter(securitySchemaName, new object[] { new[] { "myApiScope1", "myApiScope2" } }); +}); +``` + +> Note: the `OAuthAuthorizeOperationFilter` has by default `"oauth2"` as `securitySchemaName`. + +### Shared Access Key + +To indicate that an API is protected by [Shared Access Key authentication](../security/auth/shared-access-key.md), you need to add `SharedAccessKeyAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckle's Swagger generation: + +```csharp +using Arcus.WebApi.OpenApi.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.OpenApi.Models; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new new OpenApiInfo { Title = "My API v1", Version = "v1" }); + + string securitySchemaName = "my-sharedaccesskey"; + setupAction.AddSecurityDefinition(securitySchemaName, new OpenApiSecurityScheme + { + Name = "X-API-Key", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header + }); + + setupAction.OperationFilter(securitySchemaName); +}); +``` + +> Note: the `SharedAccessKeyAuthenticationOperationFilter` has by default `"sharedaccesskey"` as `securitySchemaName`. + +[← back](/) diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/certificate.md b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/certificate.md new file mode 100644 index 00000000..d80a66c4 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/certificate.md @@ -0,0 +1,156 @@ +--- +title: "Authentication with certificate via ASP.NET Core authentication filters" +layout: default +--- + +# Authentication with certificate + +The `Arcus.WebApi.Security` package provides a mechanism that uses the client certificate of the request to grant access to a web application. + +This authentication process consists of following parts: + +1. Find the client certificate configured on the HTTP request +2. Determine which properties of the received client certificate are used for authentication +3. The property value(s) of the client certificate matches the value(s) determined via configured secret provider, configuration or custom implementation + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.WebApi.Security +``` + +## Globally enforce certificate authentication + +### Introduction + +The `CertificateAuthenticationFilter` can be added to the request filters in an ASP.NET Core application. +This filter will then add authentication to all endpoints via one or many certificate properties configurable on the filter itself. + +### Usage + +The authentication requires a service dependency to be registered with the services container of the ASP.NET request pipeline, which can be one of the following: +- Arcus secret store: see [our official documentation](https://security.arcus-azure.net/features/secret-store/) for more information about setting this up. +- `Configuration`: key/value pairs in the configuration of the ASP.NET application. +- `IX509ValidationLocation`/`X509ValidationLocation`: custom or built-in implementation that retrieves the expected certificate values. + +Each certificate property that should be validated can use a different service dependency. +This mapping of what service which property uses, is defined in an `CertificateAuthenticationValidator` instance. + +Once this is done, the `CertificateAuthenticationFilter` can be added to the filters that will be applied to all actions: + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Arcus.WebApi.Security.Authentication.Certificates; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSecretStore(stores => +{ + stores.AddAzureKeyVaultWithManagedIdentity("https://your-keyvault.vault.azure.net/", CacheConfiguration.Default)); +}); + +builder.Services.AddControllers(mvcOptions => +{ + // Additional consumer-configurable options to change the behavior of the authentication filter. + mvcOptions.AddCertificateAuthenticationFilter( + auth => + { + auth.WithIssuer(X509ValidationLocation.SecretProvider, "key-to-certificate-issuer-name"); + }, + configureOptions: options => + { + // Adds certificate authentication to the request pipeline with emitting security events during the authorization of the request. + // (default: `false`) + options.EmitSecurityEvents = true; + })); +}); +``` + +## Enforce certificate authentication per controller or operation + +### Introduction + +The `CertificateAuthenticationAttribute` can be added on both controller- and operation level in an ASP.NET Core application. +This certificate authentication will then be applied to the endpoint(s) that are decorated with the `CertificateAuthenticationAttribute`. + +### Usage + +The authentication requires a service dependency to be registered with the services container of the ASP.NET request pipeline, which can be one of the following: +- Arcus secret store: see [our official documentation](https://security.arcus-azure.net/features/secret-store/) for more information about setting this up. +- `Configuration`: key/value pairs in the configuration of the ASP.NET application. +- `IX509ValidationLocation`/`X509ValidationLocation`: custom or built-in implementation that retrieves the expected certificate values + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Arcus.WebApi.Security.Authentication.Certificates; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +// See https://security.arcus-azure.net/features/secret-store/ for more information. +builder.Services.AddSecretStore(stores => +{ + stores.AddAzureKeyVaultWithManagedIdentity("https://your-keyvault.vault.azure.net/", CacheConfiguration.Default)); +}); + +builder.Services.AddCertificateAuthenticationValidation(auth => +{ + auth.WithIssuer(X509ValidationLocation.SecretProvider, "key-to-certificate-issuer-name"); +}); +``` + +After that, the `CertificateAuthenticationAttribute` attribute can be applied on the controllers, or if more fine-grained control is needed, on the operations that requires authentication: + +```csharp +using Arcus.WebApi.Security.Authentication.Certificates; + +[ApiController] +[CertificateAuthentication] +public class MyApiController : ControllerBase +{ + [HttpGet] + [Route("authz/certificate")] + public IActionResult AuthorizedGet() + { + return Ok(); + } +} +``` + +#### Configuration + +Some additional configuration options are available on the attribute. + +```csharp +// Adds certificate authentication to the request pipeline with emitting of security events during the authentication of the request. +// (default: `false`) +[CertificateAuthentication(EmitSecurityEvents = true)] +``` + +## Bypassing authentication + +The package supports a way to bypass the certificate authentication for certain endpoints. +This works with adding one of these attributes to the respectively endpoint: +- `BypassCertificateAuthentication` +- `AllowAnonymous` + +> Works on both method and controller level, using either the certificate filter or attribute. + +```csharp +using Arcus.WebApi.Security.Authentication.Certificates; + +[ApiController] +[CertificateAuthentication] +public class SystemController : ControllerBase +{ + [HttpGet('api/v1/health')] + [BypassCertificateAuthentication] + public IActionResult GetHealth() + { + return Ok(); + } +} +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/jwt.md b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/jwt.md new file mode 100644 index 00000000..2c28c918 --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/jwt.md @@ -0,0 +1,166 @@ +--- +title: "Authorization with JWT via ASP.NET Core authorization filters" +layout: default +--- + +# Authorization with JWT + +The `Arcus.WebApi.Security` package provides a mechanism that uses JWT (JSON Web Tokens) to authorize requests access to the web application. + +This authorization process consists of the following parts: +1. Find the OpenID server endpoint to request the correct access token +2. Determine the request header name you want to use where the access token should be available + +- [Authorization with JWT](#authorization-with-jwt) + - [Globally enforce JWT authorization](#globally-enforce-jwt-authorization) + - [Installation](#installation) + - [Usage](#usage) + - [Custom Claim validation](#custom-claim-validation) + - [Bypassing authentication](#bypassing-authentication) + - [Accessing secret store on JWT Bearer token authentication](#accessing-secret-store-on-jwt-bearer-token-authentication) + +## Globally enforce JWT authorization + +### Installation + +This feature requires to install our NuGet package: + +```shell +PM > Install-Package Arcus.WebApi.Security +``` + +### Usage + +The `JwtTokenAuthorizationFilter` can be added to the request filters in an ASP.NET Core application. +This filter will then add authorization to all endpoints via the configured properties on the filter itself. + +Example: + +```csharp +using Arcus.WebApi.Security.Authorization.Jwt; +using Microsoft.AspNetCore.Builder; +using Microsoft.IdentityModel.Tokens; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddControllers(mvcOptions => +{ + // Default configuration: + // By default, the JWT authorization filter will use the Microsoft 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration' OpenID endpoint to request the configuration. + mvcOptions.AddJwtTokenAuthorizationFilter(); + + mvcOptions.AddJwtTokenAuthorizationFilter(options => + { + // Default configuration with validation parameters: + // One can still use the default Microsoft OpenID endpoint and provide additional validation parameters to manipulate how the JWT token should be validated. + var parameters = new TokenValidationParameters(); + options.JwtTokenReader = new JwtTokenReader(parameters); + + // Default configuration with application ID: + // One can use the Microsoft OpenID endpoint and provide just the application ID as input for the validation parameters. + // By default will only the issuer singing keys and lifetime be validated. + string applicationId = "e98s9-sadf8981-asd8f79-ahtew8"; + options.JwtTokenReader = new JwtTokenReader(applicationId); + + // Custom OpenID endpoint: + // You can use your own custom OpenID endpoint by providing another the endpoint in the options; additionally with custom validation parameters how the JWT token should be validated. + var parameters = new TokenValidationParameters(); + string endpoint = "https://localhost:5000/.well-known/openid-configuration"; + options.JwtTokenReader = new JwtTokenReader(parameters, endpoint); + + // Emitting security events: + // One can opt-in for security events during the authorization of the request (default: `false`). + options.EmitSecurityEvents = true; + }); +}); +``` + +### Custom Claim validation + +It allows validating not only on the audience claim in the JWT token, but any type of custom claim that needs to be verified + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.IdentityModel.Tokens; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddControllers(mvcOptions => +{ + // Default configuration with issuer: + // One can use the Microsoft OpenID endpoint and provide just the issuer as input for the validation parameters. + var claimCheck = new Dictionary + { + {"aud", Issuer} + }; + mvcOptions.AddJwtTokenAuthorizationFilter(claimCheck); + + // Custom OpenID endpoint: + // You can use your own custom OpenID endpoint by providing another the endpoint in the options; additionally with custom validation parameters and custom claims to manipulate how the JWT token should be validated. + var claimCheck = new Dictionary + { + {"aud", Issuer}, + {"oid", "fa323e12-e4b8-4e22-bb2a-b18cb4b76301"} + }; + mvcOptions.AddJwtTokenAuthorizationFilter(claimCheck); + + // Default configuration with validation parameters: + // One can still use the default Microsoft OpenID endpoint and provide additional validation parameters and custom claims to manipulate how the JWT token should be validated. + var parameters = new TokenValidationParameters(); + var claimCheck = new Dictionary + { + {"aud", Issuer}, + {"oid", "fa323e12-e4b8-4e22-bb2a-b18cb4b76301"} + }; + + mvcOptions.AddJwtTokenAuthorizationFilter(claimCheck); +}}); +``` + +## Bypassing authentication + +The package supports a way to bypass the JWT authorization for certain endpoints. +This works with adding one of these attributes to the respectively endpoint: +- `BypassJwtAuthorization` +- `AllowAnonymous` + +> Works on both method and controller level. + +```csharp +using Arcus.WebApi.Security.Authorization.Jwt; + +[ApiController] +public class SystemController : ControllerBase +{ + [HttpGet('api/v1/health')] + [BypassJwtAuthorization] + public IActionResult GetHealth() + { + return Ok(); + } +} +``` + +## Accessing secret store on JWT Bearer token authentication + +This package also provides an extra extension to access the [Arcus secret store](https://security.arcus-azure.net/features/secret-store/) while configuring JWT Bearer token authentication. +Access to the secret store will help to provide, for example, issuer symmetric keys. + +```csharp +using Arcus.Security.Core; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddAuthentication(...) + .AddJwtBearer((options, serviceProvider) => + { + var secretProvider = serviceProvider.GetRequiredService(); + string key = secretProvider.GetRawSecretAsync("JwtSigningKey").GetAwaiter().GetResult(); + + options.TokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)), + }; + }); +``` \ No newline at end of file diff --git a/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/shared-access-key.md b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/shared-access-key.md new file mode 100644 index 00000000..d3ba3e9c --- /dev/null +++ b/docs/versioned_docs/version-v2.0.0/03-Features/security/auth/shared-access-key.md @@ -0,0 +1,179 @@ +--- +title: "Authentication with shared access keys via ASP.NET Core authentication filters" +layout: default +--- + +# Authentication with shared access keys + +The `Arcus.WebApi.Security` package provides a mechanism that uses shared access keys to grant access to a web application. +This authentication process consists of two parts: + +1. Find the configured parameter that holds the shared access key, this can be a request header, a query parameter or both. +2. Shared access key matches the value with the secret stored, determined via configured secret provider + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.WebApi.Security +``` + +## Globally enforce shared access key authentication + +### Introduction + +The `SharedAccessKeyAuthenticationFilter` can be added to the request filters in an ASP.NET Core application. +This filter will then add authentication to all endpoints via a shared access key configurable on the filter itself. + +### Usage + +We created a `SharedAccessKeyAuthenticationFilter` MVC filter which will be applied to all actions: + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +builder.Services.AddSecretStore(stores => +{ + stores.AddAzureKeyVaultWithManagedIdentity("https://your-keyvault.vault.azure.net/", CacheConfiguration.Default)); +}); + +builder.Services.AddControllers(options => +{ + // Adds shared access key authentication to the request pipeline where the request query string parameter will be verified + // if the query parameter value contain the expected secret value, retrievable with the given secret name. + options.AddSharedAccessKeyAuthenticationFilterOnQuery( + queryParameterName: "api-key", + secretName: "shared-access-key-name"))); + + // Adds shared access key authentication to the request pipeline where only the request header will be verified if it contains the expected secret value. + options.AddSharedAccessKeyAuthenticationFilterOnHeader( + headerName: "http-request-header-name", + secretName: "shared-access-key-name")); + + // Additional consumer-configurable options to change the behavior of the authentication filter. + options.AddSharedAccessKeyAuthenticationFilterOnHeader(..., configureOptions: opt => + { + // Adds shared access key authentication with emitting security events during the authentication of the request. + // (default: `false`) + opt.EmitSecurityEvents = true + })); +}); +``` + +For this setup to work, an Arcus secret store is required as the provided secret name (in this case `"shared-access-key-name"`) will be looked up. +See [our official documentation](https://security.arcus-azure.net/features/secret-store/) for more information about setting this up. + +> 💡 This authentication mechanism supports multiple secret versions so that many different versions of a single configured secret are checked during authentication. See [our official documentation](https://security.arcus-azure.net/Features/secret-store/versioned-secret-provider) for more information on how to configured your secret as 'versioned' in the secret store. + +## Enforce shared access key authentication per controller or operation + +### Introduction + +The `SharedAccessKeyAuthenticationAttribute` can be added on both controller- and operation level in an ASP.NET Core application. +The shared access key authentication will then be applied to the endpoint(s) that are decorated with the `SharedAccessKeyAuthenticationAttribute`. + +### Usage + +We created an `SharedAccessKeyAuthenticationAttribute` attribute which can be applied on the controllers, or if more fine-grained control is needed, on the operations that requires authentication: + +```csharp +using Arcus.WebApi.Security.Authentication.SharedAccessKey; + +[ApiController] +[SharedAccessKeyAuthentication(headerName: "http-request-header-name", queryParameterName: "api-key", secretName: "shared-access-key-name")] +public class MyApiController : ControllerBase +{ + [HttpGet] + [Route("authz/shared-access-key")] + public IActionResult AuthorizedGet() + { + return Ok(); + } +} +``` + +For this setup to work, an Arcus secret store is required as the provided secret name (in this case `"shared-access-key-name"`) will be looked up. +See [our official documentation](https://security.arcus-azure.net/features/secret-store/) for more information about setting this up. + +> 💡 This authentication mechanism supports multiple secret versions so that many different versions of a single configured secret are checked during authentication. See [our official documentation](https://security.arcus-azure.net/Features/secret-store/versioned-secret-provider) for more information on how to configured your secret as 'versioned' in the secret store. + +#### Configuration + +Some additional configuration options are available on the attribute. + +```csharp +// Adds shared access key authentication with emitting of security events during the authentication of the request. +// (default: `false`) +[SharedAccessKeyAuthentication(..., EmitSecurityEvents: true)] +``` + +## Behavior in validating shared access key parameter +The package supports different scenarios for specifying the shared access key parameter and is supported for global or per controller/operation use cases. + +- **Use header** - Only the specified request header will be validated for the shared access key, any supplied query parameter will not be taken into account. + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +// See https://security.arcus-azure.net/features/secret-store/ for more information. +builder.Services.AddSecretStore(stores => +{ + stores.AddAzureKeyVaultWithManagedIdentity("https://your-keyvault.vault.azure.net/", CacheConfiguration.Default)); +}); + +builder.Services.AddControllers(options => +{ + options.AddSharedAccessKeyAuthenticationFilterOnHeader(headerName: "http-request-header-name", secretName: "shared-access-key-name")); +}); +``` + +- **Use query parameter** - Only the specified query parameter will be validated for the shared access key, any supplied request header will not be taken into account. + +```csharp +using Arcus.Security.Core.Caching.Configuration; +using Microsoft.AspNetCore.Builder; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(); + +// See https://security.arcus-azure.net/features/secret-store/ for more information. +builder.Services.AddSecretStore(stores => +{ + stores.AddAzureKeyVaultWithManagedIdentity("https://your-keyvault.vault.azure.net/", CacheConfiguration.Default)); +}); + +builder.Services.AddControllers(options => +{ + options.AddSharedAccessKeyAuthenticationFilterOnQuery(queryParameterName: "api-key", secretName: "shared-access-key-name")); +}); +``` + +## Bypassing authentication +The package supports a way to bypass the shared access key authentication for certain endpoint. +This works with adding one of these attributes to the respectively endpoint: +- `BypassSharedAccessKeyAuthentication` +- `AllowAnonymous` + +> Works on both method and controller level, using either the shared access key filter or attribute. + +```csharp +using Arcus.WebApi.Security.Authentication.SharedAccessKey; + +[ApiController] +[SharedAccessKeyAuthentication("MySecret", "MyHeader")] +public class SystemController : ControllerBase +{ + [HttpGet('api/v1/health')] + [BypassSharedAccessKeyAuthentication] + public IActionResult GetHealth() + { + return Ok(); + } +} +``` diff --git a/docs/versioned_sidebars/version-v2.0.0-sidebars.json b/docs/versioned_sidebars/version-v2.0.0-sidebars.json new file mode 100644 index 00000000..caea0c03 --- /dev/null +++ b/docs/versioned_sidebars/version-v2.0.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "tutorialSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/docs/versions.json b/docs/versions.json index 22ea8e9b..828e290e 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,4 +1,5 @@ [ + "v2.0.0", "v1.7.0", "v1.6.0", "v1.5.0",