< Summary

Information
Class: ArturRios.Util.WebApi.Security.Authentication.JwtTokenValidator
Assembly: ArturRios.Util.WebApi
File(s): /home/runner/work/dotnet-webapi-util/dotnet-webapi-util/src/Security/Authentication/JwtTokenValidator.cs
Line coverage
96%
Covered lines: 30
Uncovered lines: 1
Coverable lines: 31
Total lines: 91
Line coverage: 96.7%
Branch coverage
92%
Covered branches: 13
Total branches: 14
Branch coverage: 92.8%
Method coverage
100%
Covered methods: 4
Fully covered methods: 3
Total methods: 4
Method coverage: 100%
Full method coverage: 75%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ValidateAsync()92.85%141494.44%
MapUser(...)100%11100%
MapId(...)100%11100%

File(s)

/home/runner/work/dotnet-webapi-util/dotnet-webapi-util/src/Security/Authentication/JwtTokenValidator.cs

#LineLine coverage
 1using ArturRios.Jwt;
 2using ArturRios.Util.WebApi.Security.Configuration;
 3using ArturRios.Util.WebApi.Security.Enums;
 4using ArturRios.Util.WebApi.Security.Interfaces;
 5using ArturRios.Util.WebApi.Security.Records;
 6using Microsoft.AspNetCore.Http;
 7using Microsoft.Extensions.DependencyInjection;
 8
 9namespace ArturRios.Util.WebApi.Security.Authentication;
 10
 11/// <summary>Validates the app's own HMAC-signed JWT and resolves the user through the registered
 12/// <see cref="IAuthenticatedUserMapper"/> — from claims alone or by an <see cref="IAuthenticationProvider"/>
 13/// lookup, per <see cref="AuthenticationOptions.JwtMode"/>.</summary>
 14/// <param name="jwtConfig">Provides the key material used to validate the token.</param>
 15/// <param name="jwtHandler">Validates token signatures.</param>
 16/// <param name="mapper">Interprets the token's claims as the app's user.</param>
 17/// <param name="options">Controls how the user is resolved once the signature is valid.</param>
 3418public class JwtTokenValidator(
 3419    JwtConfiguration jwtConfig,
 3420    JwtHandler jwtHandler,
 3421    IAuthenticatedUserMapper mapper,
 3422    AuthenticationOptions options) : ITokenValidator
 23{
 24    /// <inheritdoc />
 25    public async Task<TokenValidationResult> ValidateAsync(string token, HttpContext context)
 26    {
 27        // Every configured key when there are any, so a token signed with a key that is no longer
 28        // the signing key stays valid until it is withdrawn — which is what makes replacing a signing
 29        // secret a rotation rather than a cutover that invalidates every token in flight. Falls back
 30        // to the single secret when no keys are configured, which is what every configuration written
 31        // before ArturRios.Jwt 1.1.0 looks like.
 2732        var isValid = jwtConfig.Keys.Count > 0
 2733            ? await jwtHandler.IsTokenValidAsync(token, jwtConfig.Keys)
 2734            : await jwtHandler.IsTokenValidAsync(token, jwtConfig.Secret);
 35
 2736        if (!isValid)
 37        {
 938            return new TokenValidationResult(null, "Invalid token");
 39        }
 40
 1841        var claims = TokenClaimsReader.Read(token);
 42
 1843        if (claims is null)
 44        {
 045            return new TokenValidationResult(null, "Could not read token claims");
 46        }
 47
 1848        if (options.JwtMode == JwtValidationMode.ClaimsOnly)
 49        {
 1350            var claimsUser = MapUser(claims);
 51
 1352            return new TokenValidationResult(claimsUser, claimsUser is null ? "Could not retrieve user from token" : nul
 53        }
 54
 555        var userId = MapId(claims);
 56
 557        if (userId is null)
 58        {
 259            return new TokenValidationResult(null, "Could not retrieve user id from token");
 60        }
 61
 362        var provider = context.RequestServices.GetRequiredService<IAuthenticationProvider>();
 363        var user = provider.GetAuthenticatedUserById(userId.Value);
 64
 365        return new TokenValidationResult(user, user is null ? "User not found" : null);
 2766    }
 67
 68    private IAuthenticatedUser? MapUser(IReadOnlyDictionary<string, string> claims)
 69    {
 70        try
 71        {
 1372            return mapper.FromClaims(claims);
 73        }
 174        catch (Exception)
 75        {
 176            return null;
 77        }
 1378    }
 79
 80    private Guid? MapId(IReadOnlyDictionary<string, string> claims)
 81    {
 82        try
 83        {
 584            return mapper.IdFromClaims(claims);
 85        }
 186        catch (Exception)
 87        {
 188            return null;
 89        }
 590    }
 91}