I'm writing a Razor Pages application running on .Net 5.0. This application needs to be able to support members of staff (who sign in with Windows authentication) and applicants (who don't have a Windows account, so need to register/login with a custom authentication process). I can either get Windows auth or custom auth to work, but the two don't want to play ball together!!
I believe I need to write a custom implementation of IAuthenticationService
but this is where it's tripping me up. I can't work out what I need to do in ChallengeAsync
in order to let the challenge pass!
Here's the AuthService implementation at the moment (yes, it's not the nicest, but my focus at this point is on getting it working!!):
public class AuthService : IAuthenticationService
{
async Task<AuthenticateResult> IAuthenticationService.AuthenticateAsync(HttpContext context, string scheme)
{
if (HasAnonymousAttribute(context))
{
return AuthenticateResult.NoResult();
}
var user = getUser(context);
if (user != null)
{
var ticket = new AuthenticationTicket(user, "magic");
return AuthenticateResult.Success(ticket);
}
await context.ChallengeAsync("Windows");
if (context.User.Identity.IsAuthenticated)
{
var ticket = new AuthenticationTicket(context.User, "Windows");
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Please log in");
}
Task IAuthenticationService.ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
var user = context.Session.Get("User");
if (user == null)
{
//do something to block the user from access
}
return Task.FromResult(0);
}
Task IAuthenticationService.ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
throw new NotImplementedException();
}
Task IAuthenticationService.SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
{
if(scheme.ToLower() == "magic")
{
context.Session.Set("User", Encoding.ASCII.GetBytes(principal.Identity.Name));
}
return Task.FromResult(0);
}
Task IAuthenticationService.SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
throw new NotImplementedException();
}
private ClaimsPrincipal getUser(HttpContext context)
{
if (context.User.Identity.IsAuthenticated)
{
return (ClaimsPrincipal)context.User.Identity;
}
return null;
}
private bool HasAnonymousAttribute(HttpContext context)
{
var endpoint = context.GetEndpoint();
var retVal = (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null);
return retVal;
}
}
You should implement only the methods in which you need custom logic.
So instead of implementing the interface, you could subclass the regular Asp.Net AuthenticationService
, then put custom logic in AuthenticateAsync
but don't override ChallengeAsync
.
using Microsoft.AspNetCore.Authentication;
namespace Some.Lovely.Namespace
{
public class MyCustomAuthenticationService : AuthenticationService
{
public CustomAuthenticationService(
[NotNull] IAuthenticationSchemeProvider schemes,
[NotNull] IAuthenticationHandlerProvider handlers,
[NotNull] IClaimsTransformation transform,
[NotNull][ItemNotNull] IOptions<AuthenticationOptions> options) :
base(schemes, handlers, transform, options)
{ }
public override async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string? scheme)
{
// your custom logic
}
}
}
Then register the service like so in Startup.cs
:
services.AddScoped<IAuthenticationService, MyCustomAuthenticationService >();