Suggestion: Include a way to stub a specific authentication schema
egil opened this issue · 1 comments
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.