< Summary

Information
Class: ArturRios.Util.WebApi.Security.Middleware.AuthenticationMiddleware
Assembly: ArturRios.Util.WebApi
File(s): D:\Repositories\dotnet-webapi-util\src\Security\Middleware\AuthenticationMiddleware.cs
Line coverage
95%
Covered lines: 39
Uncovered lines: 2
Coverable lines: 41
Total lines: 93
Line coverage: 95.1%
Branch coverage
70%
Covered branches: 14
Total branches: 20
Branch coverage: 70%
Method coverage
100%
Covered methods: 4
Fully covered methods: 1
Total methods: 4
Method coverage: 100%
Full method coverage: 25%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
InvokeAsync()83.33%1212100%
WriteUnauthorized()50%4480%
IsSwaggerRoute(...)50%44100%

File(s)

D:\Repositories\dotnet-webapi-util\src\Security\Middleware\AuthenticationMiddleware.cs

#LineLine coverage
 1using ArturRios.Configuration.Providers;
 2using ArturRios.Output;
 3using ArturRios.Util.WebApi.Configuration;
 4using ArturRios.Util.WebApi.Middleware;
 5using ArturRios.Util.WebApi.Security.Attributes;
 6using ArturRios.Util.WebApi.Security.Authentication;
 7using ArturRios.Util.WebApi.Security.Configuration;
 8using ArturRios.Util.WebApi.Security.Interfaces;
 9using ArturRios.Util.WebApi.Security.Records;
 10using Microsoft.AspNetCore.Http;
 11using Newtonsoft.Json;
 12
 13namespace ArturRios.Util.WebApi.Security.Middleware;
 14
 15/// <summary>
 16/// Authenticates requests by extracting one token (from the header, a cookie, or either, per
 17/// <see cref="AuthenticationOptions.Source"/>) and running it through the enabled
 18/// <see cref="ITokenValidator"/>s in order; the first that resolves a user attaches it to
 19/// <c>HttpContext.Items["User"]</c>. Swagger and <see cref="AllowAnonymousAttribute"/> endpoints are skipped.
 20/// </summary>
 21/// <param name="next">The next middleware in the pipeline.</param>
 22/// <param name="settings">Configuration used to detect Swagger routes.</param>
 23/// <param name="options">Controls the token source and which validators run.</param>
 24/// <param name="validators">The enabled validators, tried in registration order (app JWT first, Google second).</param>
 625public class AuthenticationMiddleware(
 626    RequestDelegate next,
 627    SettingsProvider settings,
 628    AuthenticationOptions options,
 629    IEnumerable<ITokenValidator> validators) : WebApiMiddleware
 30{
 631    private readonly ITokenValidator[] _validators = validators.ToArray();
 32
 33    /// <summary>Validates the request token and, on success, attaches the authenticated user before invoking the next m
 34    /// <param name="context">The current HTTP context.</param>
 35    public async Task InvokeAsync(HttpContext context)
 636    {
 637        var endpoint = context.GetEndpoint();
 38
 639        var skipRoute =
 640            IsSwaggerRoute(context.Request.Path.Value ?? string.Empty) ||
 641            endpoint?.Metadata.GetMetadata<AllowAnonymousAttribute>() is not null;
 42
 643        if (skipRoute)
 144        {
 145            await next(context);
 46
 147            return;
 48        }
 49
 550        var token = TokenExtractor.Extract(context, options.Source, options.CookieName);
 51
 552        string? lastError = null;
 53
 2354        foreach (var validator in _validators)
 655        {
 656            var (user, error) = await validator.ValidateAsync(token, context);
 57
 658            if (user is not null)
 459            {
 460                context.Items["User"] = user;
 61
 462                await next(context);
 63
 464                return;
 65            }
 66
 267            lastError = error;
 268        }
 69
 170        await WriteUnauthorized(context, lastError);
 671    }
 72
 73    private static async Task WriteUnauthorized(HttpContext context, string? authError)
 174    {
 175        var output = ProcessOutput.New.WithError(authError ?? "Unauthorized");
 76
 177        if (context.Response.HasStarted)
 078        {
 079            return;
 80        }
 81
 182        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
 183        context.Response.ContentType = "application/json";
 84
 185        var payload = JsonConvert.SerializeObject(output);
 86
 187        await context.Response.WriteAsync(payload);
 188    }
 89
 90    private bool IsSwaggerRoute(string path) =>
 691        settings.GetBool(AppSettingsKeys.SwaggerEnabled) is true &&
 692        path.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase);
 93}