5

I'm quite frustrated about the fact that an authentication scheme appears to be mandatory in Asp.Net Core. My objective is to build an API and I don't want to know anything about the client. I've built custom authentication and authorization, which works fine. I'm not using identity or cookies. However, I can't return a 403 Forbid result without a valid authentication scheme, otherwise I get the following exception...

System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic

My question is, can I configure MVC to not use an authentication scheme or create an authentication scheme without the reliance on a login path or any path for that matter?

Nitesh
  • 704
  • 1
  • 7
  • 13
  • 1
    Please post some code so we can help you figure it out. – Brad Oct 01 '16 at 13:16
  • 1
    @Brad This is more of an architectural question. There's no specific code that I'm having an issue with. Basically if you use an Authorize attribute or most things dealing with User.Identity, you require an Authentication Scheme of some sort. – Nitesh Oct 01 '16 at 14:54

1 Answers1

2

After poring over the Asp.net Core security source code, I've managed to create a custom authentication handler. To do this you need to implement 3 classes.

The first class implements an abstract AuthenticationOptions.

public class AwesomeAuthenticationOptions : AuthenticationOptions {
    public AwesomeAuthenticationOptions() {
        AuthenticationScheme = "AwesomeAuthentication";
        AutomaticAuthenticate = false;
    }
}

The second class implements an abstract AuthenticationHandler.

public class AwesomeAuthentication : AuthenticationHandler<AwesomeAuthenticationOptions>
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var prop = new AuthenticationProperties();
        var ticket = new AuthenticationTicket(Context.User, prop, "AwesomeAuthentication");
        //this is where you setup the ClaimsPrincipal
        //if auth fails, return AuthenticateResult.Fail("reason for failure");
        return await Task.Run(() => AuthenticateResult.Success(ticket));
    }
}

The third class implements an abstract AuthenticationMiddleware.

public class AwesomeAuthenticationMiddleware : AuthenticationMiddleware<AwesomeAuthenticationOptions>
{
    public AwesomeAuthenticationMiddleware(RequestDelegate next, 
        IOptions<AwesomeAuthenticationOptions> options,
        ILoggerFactory loggerFactory,
        UrlEncoder urlEncoder) : base(next, options, loggerFactory, urlEncoder) {

    }

    protected override AuthenticationHandler<AwesomeAuthenticationOptions> CreateHandler()
    {
        return new AwesomeAuthentication();
    }
}

Finally, you use the middleware component in the Startup.cs Configure method.

app.UseMiddleware<AwesomeAuthenticationMiddleware>();

Now you can build your own Authentication Schemes.

Nitesh
  • 704
  • 1
  • 7
  • 13
  • You don't need, and shouldn't do return Task.Run(). Do return AuthenticateResult.Success(ticket); instead. – blowdart Oct 02 '16 at 13:31
  • Also generally if you have no other authentication middleware Context.User is going to be the anonymous user. The whole point of an Authentication handler is to set everything up to populate Context.User. Using it inside HandleAuthenticateAsync() is prone to failure. – blowdart Oct 02 '16 at 13:32
  • @blowdart I've corrected the returning of Task.Run(). I understand the point of this is to setup the principal, but I added this code as an outline over here. – Nitesh Oct 02 '16 at 15:31
  • Also when I use the attribute [Authorize("PolicyName")] and the use isn't authenticated, I receive a 403. At this point I'm expecting a 401. Any idea why? – Nitesh Oct 02 '16 at 15:33
  • Because you aren't handling the overrides that do 401 versus 403 – blowdart Oct 02 '16 at 15:36
  • Take a look at the cookie middleware source to see what events you need to handle. Writing your own is tricky. – blowdart Oct 03 '16 at 00:35
  • 1
    Thanks for the feedback. It is tricky, but I think I'm getting the hang of it. Once I implemented the AuthenticationHandler correctly, everything started working as expected. I didn't have to override the handlers for unauthorized and forbidden as they did what I wanted. Returning AuthenticateResult.Fail("reason"); is what sets the IsAuthenticated on the principal to false, which then returns a 401 on a challenge. – Nitesh Oct 03 '16 at 07:22
  • Have you setup custom authentication handler? can you please share more info? – blue Oct 05 '17 at 19:52
  • @blue and others who land here: note that this all changed *somewhat* from 1.1 to 2.0. Now the authentication middleware is somewhat "static"--it shouldn't need to be customized. Now all customization rests on the *handler* side. See https://stackoverflow.com/q/45805411/85269 and https://github.com/ignas-sakalauskas/CustomAuthenticationNetCore20 for more info. This still seems relatively new, so resources are few and far between. – Marc L. Jan 19 '18 at 22:14