JasperFx/alba

Suggestion: Include a way to stub a specific authentication schema

egil opened this issue · 1 comments

egil commented

Background: I have an API where the endpoints have a specific authentication schema specified via the Authorize attribute, e.g. [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] because the project supports multiple different auth schemas.

Unfortunately, that means the built-in AuthenticationStub in Alba doesn't work, since it replaces the existing schemas with the "Test" schema.

The workaround I ended up with is the following type, which you may want to include in Alba, or you may want to do something else that achieves the same goal:

public sealed class AuthenticationSchemaStub : AuthenticationExtensionBase, IAlbaExtension
{
    private const string TestSchemaName = "Test";

    internal string OverrideSchemaTargetName { get; }

    public AuthenticationSchemaStub(string overrideSchemaTargetName)
        => OverrideSchemaTargetName = overrideSchemaTargetName;

    void IDisposable.Dispose()
    {
        // nothing to dispose
    }

    ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;

    Task IAlbaExtension.Start(IAlbaHost host) => Task.CompletedTask;

    IHostBuilder IAlbaExtension.Configure(IHostBuilder builder)
    {
        return builder.ConfigureServices(services =>
        {
            services.AddSingleton(this);
            services.AddTransient<IAuthenticationSchemeProvider, MockSchemeProvider>();
        });
    }

    internal ClaimsPrincipal BuildPrincipal(HttpContext context)
    {
        var claims = allClaims(context);
        var identity = new ClaimsIdentity(claims, TestSchemaName);
        var principal = new ClaimsPrincipal(identity);
        return principal;
    }

    private sealed class MockSchemeProvider : AuthenticationSchemeProvider
    {
        private readonly string overrideSchemaTargetName;

        public MockSchemeProvider(AuthenticationSchemaStub authSchemaStub, IOptions<AuthenticationOptions> options)
            : base(options)
        {
            overrideSchemaTargetName = authSchemaStub.OverrideSchemaTargetName;
        }

        public override Task<AuthenticationScheme> GetSchemeAsync(string name)
        {
            if (name.Equals(overrideSchemaTargetName, StringComparison.OrdinalIgnoreCase))
            {
                var scheme = new AuthenticationScheme(
                    TestSchemaName,
                    TestSchemaName,
                    typeof(MockAuthenticationHandler));

                return Task.FromResult(scheme);
            }

            return base.GetSchemeAsync(name);
        }

        private sealed class MockAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
        {
            private readonly AuthenticationSchemaStub authenticationSchemaStub;

            public MockAuthenticationHandler(
                AuthenticationSchemaStub authenticationSchemaStub,
                IOptionsMonitor<AuthenticationSchemeOptions> options,
                ILoggerFactory logger,
                UrlEncoder encoder,
                ISystemClock clock)
                : base(options, logger, encoder, clock)
            {
                this.authenticationSchemaStub = authenticationSchemaStub;
            }

            protected override Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                var principal = authenticationSchemaStub.BuildPrincipal(Context);
                var ticket = new AuthenticationTicket(principal, TestSchemaName);
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
        }
    }
}

It works much like Alba's AuthenticationStub, you can add claims to it, etc., the difference is that you specify which schema the stub should replace, so now you can have it replace a specific schema, e.g. new AuthenticationSchemaStub(JwtBearerDefaults.AuthenticationScheme).

Related to #135.

A modified version of this has been merged into the v8 branch, alongside equivalent changes for the JWT stub. Thanks for the contribution.