< Summary

Information
Class: ArturRios.Util.WebApi.Security.Extensions.AuthenticationServiceCollectionExtensions
Assembly: ArturRios.Util.WebApi
File(s): /home/runner/work/dotnet-webapi-util/dotnet-webapi-util/src/Security/Extensions/AuthenticationServiceCollectionExtensions.cs
Line coverage
100%
Covered lines: 29
Uncovered lines: 0
Coverable lines: 29
Total lines: 111
Line coverage: 100%
Branch coverage
87%
Covered branches: 14
Total branches: 16
Branch coverage: 87.5%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddCachedAuthenticationProvider(...)75%44100%
AddTokenAuthentication(...)91.66%1212100%

File(s)

/home/runner/work/dotnet-webapi-util/dotnet-webapi-util/src/Security/Extensions/AuthenticationServiceCollectionExtensions.cs

#LineLine coverage
 1using ArturRios.Util.WebApi.Security.Authentication;
 2using ArturRios.Util.WebApi.Security.Configuration;
 3using ArturRios.Util.WebApi.Security.Interfaces;
 4using ArturRios.Util.WebApi.Security.Mappers;
 5using ArturRios.Util.WebApi.Security.Providers;
 6using Microsoft.Extensions.Caching.Memory;
 7using Microsoft.Extensions.DependencyInjection;
 8using Microsoft.Extensions.DependencyInjection.Extensions;
 9
 10namespace ArturRios.Util.WebApi.Security.Extensions;
 11
 12/// <summary>
 13/// Dependency-injection helpers for registering authentication providers.
 14/// </summary>
 15public static class AuthenticationServiceCollectionExtensions
 16{
 17    /// <param name="services">The service collection to register into.</param>
 18    extension(IServiceCollection services)
 19    {
 20        /// <summary>
 21        /// Registers <typeparamref name="TProvider"/> as the underlying <see cref="IAuthenticationProvider"/> and
 22        /// exposes it through a <see cref="CachedAuthenticationProvider"/>, so repeated user lookups within the
 23        /// configured time-to-live are served from an <see cref="IMemoryCache"/> instead of the store.
 24        /// </summary>
 25        /// <typeparam name="TProvider">The concrete provider that performs the actual (e.g. database) lookup.</typepara
 26        /// <param name="configure">Optional callback to configure caching behavior (time-to-live, negative caching).</p
 27        /// <returns>The same <paramref name="services"/> instance, for chaining.</returns>
 28        /// <exception cref="ArgumentOutOfRangeException">The configured time-to-live is not positive.</exception>
 29        public IServiceCollection AddCachedAuthenticationProvider<TProvider>(
 30            Action<CachedAuthenticationProviderOptions>? configure = null)
 31            where TProvider : class, IAuthenticationProvider
 32        {
 233            var options = new CachedAuthenticationProviderOptions();
 234            configure?.Invoke(options);
 35
 236            if (options.Ttl <= TimeSpan.Zero)
 37            {
 138                throw new ArgumentOutOfRangeException(nameof(configure), options.Ttl,
 139                    "CachedAuthenticationProviderOptions.Ttl must be positive.");
 40            }
 41
 142            services.AddMemoryCache();
 143            services.AddScoped<TProvider>();
 44
 145            services.AddScoped<IAuthenticationProvider>(serviceProvider =>
 246                new CachedAuthenticationProvider(
 247                    serviceProvider.GetRequiredService<TProvider>(),
 248                    serviceProvider.GetRequiredService<IMemoryCache>(),
 249                    options));
 50
 151            return services;
 52        }
 53
 54        /// <summary>
 55        /// Registers the consolidated <see cref="AuthenticationOptions"/>, the enabled token validators, and
 56        /// <see cref="DefaultAuthenticatedUserMapper"/> as the claims mapper, unless an
 57        /// <see cref="IAuthenticatedUserMapper"/> is already registered. Use the generic overload taking a
 58        /// <c>TMapper</c> to supply your own mapper instead.
 59        /// </summary>
 60        /// <param name="configure">Configures the options.</param>
 61        /// <exception cref="ArgumentException">No scheme enabled, or Google enabled without any client IDs.</exception>
 62        public IServiceCollection AddTokenAuthentication(Action<AuthenticationOptions> configure) =>
 1063            services.AddTokenAuthentication<DefaultAuthenticatedUserMapper>(configure);
 64
 65        /// <summary>
 66        /// Registers the consolidated <see cref="AuthenticationOptions"/>, the enabled token validators, and
 67        /// <typeparamref name="TMapper"/> as the claims mapper, unless an <see cref="IAuthenticatedUserMapper"/>
 68        /// is already registered. Validators are registered app-JWT first, Google second, so the middleware
 69        /// tries them in that order. The app must separately register <c>JwtConfiguration</c> and
 70        /// <c>JwtHandler</c> (for JWT) and an <see cref="IAuthenticationProvider"/> (required for Google and
 71        /// for JWT <c>Revalidate</c> mode). Calling it again does not register a validator twice.
 72        /// </summary>
 73        /// <typeparam name="TMapper">The mapper translating between the app's user and its token claims.</typeparam>
 74        /// <param name="configure">Configures the options.</param>
 75        /// <exception cref="ArgumentException">No scheme enabled, or Google enabled without any client IDs.</exception>
 76        public IServiceCollection AddTokenAuthentication<TMapper>(Action<AuthenticationOptions> configure)
 77            where TMapper : class, IAuthenticatedUserMapper
 78        {
 1179            ArgumentNullException.ThrowIfNull(configure);
 80
 1181            var options = new AuthenticationOptions();
 1182            configure(options);
 83
 1184            if (!options.EnableJwt && !options.EnableGoogle)
 85            {
 186                throw new ArgumentException("At least one authentication scheme (JWT or Google) must be enabled.");
 87            }
 88
 1089            if (options.EnableGoogle && options.GoogleClientIds.Count == 0)
 90            {
 191                throw new ArgumentException("EnableGoogle requires at least one entry in GoogleClientIds.");
 92            }
 93
 994            services.AddSingleton(options);
 995            services.TryAddSingleton<IAuthenticatedUserMapper, TMapper>();
 96
 997            if (options.EnableJwt)
 98            {
 999                services.TryAddEnumerable(ServiceDescriptor.Singleton<ITokenValidator, JwtTokenValidator>());
 100            }
 101
 9102            if (options.EnableGoogle)
 103            {
 1104                services.TryAddSingleton<IGoogleTokenVerifier, GoogleTokenVerifier>();
 1105                services.TryAddEnumerable(ServiceDescriptor.Singleton<ITokenValidator, GoogleTokenValidator>());
 106            }
 107
 9108            return services;
 109        }
 110    }
 111}