< Summary

Information
Class: ArturRios.Util.WebApi.Security.Middleware.AuthenticationMiddleware
Assembly: ArturRios.Util.WebApi
File(s): /home/runner/work/dotnet-webapi-util/dotnet-webapi-util/src/Security/Middleware/AuthenticationMiddleware.cs
Line coverage
100%
Covered lines: 26
Uncovered lines: 0
Coverable lines: 26
Total lines: 80
Line coverage: 100%
Branch coverage
72%
Covered branches: 13
Total branches: 18
Branch coverage: 72.2%
Method coverage
100%
Covered methods: 4
Fully covered methods: 4
Total methods: 4
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
InvokeAsync()100%1010100%
WriteUnauthorized(...)50%44100%
IsSwaggerRoute(...)25%44100%

File(s)

/home/runner/work/dotnet-webapi-util/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.Extensions;
 5using ArturRios.Util.WebApi.Middleware;
 6using ArturRios.Util.WebApi.Security.Attributes;
 7using ArturRios.Util.WebApi.Security.Configuration;
 8using ArturRios.Util.WebApi.Security.Extensions;
 9using ArturRios.Util.WebApi.Security.Interfaces;
 10using Microsoft.AspNetCore.Http;
 11
 12namespace ArturRios.Util.WebApi.Security.Middleware;
 13
 14/// <summary>
 15/// Authenticates requests by extracting one token (from the header, a cookie, or either, per
 16/// <see cref="AuthenticationOptions.Source"/>) and running it through the enabled
 17/// <see cref="ITokenValidator"/>s in order; the first that resolves a user attaches it to
 18/// <c>HttpContext.Items["User"]</c>. Swagger and <see cref="AllowAnonymousAttribute"/> endpoints are skipped.
 19/// </summary>
 20/// <param name="next">The next middleware in the pipeline.</param>
 21/// <param name="settings">Configuration used to detect Swagger routes.</param>
 22/// <param name="options">Controls the token source and which validators run.</param>
 23/// <param name="validators">The enabled validators, tried in registration order (app JWT first, Google second).</param>
 2024public class AuthenticationMiddleware(
 2025    RequestDelegate next,
 2026    SettingsProvider settings,
 2027    AuthenticationOptions options,
 2028    IEnumerable<ITokenValidator> validators) : WebApiMiddleware
 29{
 30    private const string MissingTokenError = "Authentication token not provided";
 31    private const string UnauthorizedError = "Unauthorized";
 32
 2033    private readonly ITokenValidator[] _validators = validators.ToArray();
 34
 35    /// <summary>Validates the request token and, on success, attaches the authenticated user before invoking the next m
 36    /// <param name="context">The current HTTP context.</param>
 37    public async Task InvokeAsync(HttpContext context)
 38    {
 1639        if (context.GetEndpoint().AllowsAnonymous() || IsSwaggerRoute(context.Request.Path))
 40        {
 341            await next(context);
 42
 343            return;
 44        }
 45
 1346        var token = context.ExtractToken(options.Source, options.CookieName);
 47
 1348        string? lastError = null;
 49
 4750        foreach (var validator in _validators)
 51        {
 1452            var (user, error) = await validator.ValidateAsync(token, context);
 53
 1454            if (user is not null)
 55            {
 756                context.SetUser(user);
 57
 758                await next(context);
 59
 760                return;
 61            }
 62
 763            lastError = error;
 64        }
 65
 66        // Validators still run without a token (the contract allows it, for schemes that read something
 67        // else off the request); the error just says plainly what was missing.
 668        await WriteUnauthorized(context, string.IsNullOrEmpty(token) ? MissingTokenError : lastError);
 1669    }
 70
 71    private static Task WriteUnauthorized(HttpContext context, string? authError) =>
 672        context.Response.HasStarted
 673            ? Task.CompletedTask
 674            : context.Response.WriteOutputAsync(StatusCodes.Status401Unauthorized,
 675                ProcessOutput.New.WithError(authError ?? UnauthorizedError));
 76
 77    private bool IsSwaggerRoute(PathString path) =>
 1378        path.StartsWithSegments("/swagger", StringComparison.OrdinalIgnoreCase) &&
 1379        settings.GetBool(AppSettingsKeys.SwaggerEnabled) is true;
 80}