19

I'm trying to build a custom AuthenticationHandler in ASP.Net Core 2. Following up topic like ASP.NET Core 2.0 authentication middleware and Why is Asp.Net Core Authentication Scheme mandatory, I've created the specific classes. The registering happens like this:

services.AddAuthentication(
    options =>
    {
        options.DefaultScheme = Constants.NoOpSchema;
        options.DefaultAuthenticateScheme = Constants.NoOpSchema;
        options.DefaultChallengeScheme = Constants.NoOpSchema;
        options.DefaultSignInScheme = Constants.NoOpSchema;
        options.DefaultSignOutScheme = Constants.NoOpSchema; 
        options.DefaultForbidScheme = Constants.NoOpSchema;
    }
).AddScheme<CustomAuthOptions, CustomAuthHandler>(Constants.NoOpSchema, "Custom Auth", o => { });

Everything works, if the specific controllers set the Scheme explicitely:

[Authorize(AuthenticationSchemes= Constants.NoOpSchema)]
[Route("api/[controller]")]
public class IndividualsController : Controller

But I would like to not have to set the Schema, since it should get added dynamically. As soon as I remove the Scheme Property, like this:

[Authorize]
[Route("api/[controller]")]
public class IndividualsController : Controller

It doesn't work anymore.

I would have hoped, that setting the DefaultScheme Properties does this job. Interesting enough, I didn't find any specific discussion about this topic. Am I doing something wrong here or is my expected outcome wrong?

Edit: Thanks for the questions, it helped me a lot. It seems like mapping the DefaultScheme is using by the Authentication Middleware, which I only used, when the CustomAuthHandler was not in place. Therefore I had to add the AuthenticationMiddleware always.

Edit2: Unfortunately, it still doesn't work. To enhance a bit my question: I'm adding the middleware as usual:

app.UseAuthentication();
app.UseMvc();

Now I get into my Handler, which is looking like this:

public class NoOpAuthHandler : AuthenticationHandler<NoOpAuthOptions>
{
    public const string NoOpSchema = "NoOp";

    public NoOpAuthHandler(IOptionsMonitor<NoOpAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync() => Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, NoOpSchema)));
}

But even if I always return success, I get an 401. I think I have to dig deeper and kindahow set some Claims, but unfortunately, the Handlers from Microsoft are quite hard to analyze, since they contain a lot of Code.

MasLoo
  • 1,240
  • 2
  • 8
  • 23
Matthias Müller
  • 3,064
  • 2
  • 28
  • 55

5 Answers5

18

ASP.Net Core 2 Answer

You have to set a default authorization policy tied to your authentication scheme:

services.AddAuthorization(options => {
  options.DefaultPolicy = new AuthorizationPolicyBuilder()
    .AddAuthenticationSchemes(Constants.NoOpSchema)
    .RequireAuthenticatedUser()
    .Build();
});

ASP.Net Core 3 Answer

In ASP.Net Core 3 things apparently changed a bit, so you would want to create an extension method to add your authentication handler:

public static class NoOpAuthExtensions
{
    public static AuthenticationBuilder AddNoOpAuth(this AuthenticationBuilder builder)
        => builder.AddNoOpAuth(NoOpAuthHandler.NoOpSchema, _ => { });
    public static AuthenticationBuilder AddNoOpAuth(this AuthenticationBuilder builder, Action<NoOpAuthOptions> configureOptions)
        => builder.AddNoOpAuth(NoOpAuthHandler.NoOpSchema, configureOptions);
    public static AuthenticationBuilder AddNoOpAuth(this AuthenticationBuilder builder, string authenticationScheme, Action<NoOpAuthOptions> configureOptions)
        => builder.AddNoOpAuth(authenticationScheme, null, configureOptions);
    public static AuthenticationBuilder AddNoOpAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<NoOpAuthOptions> configureOptions)
    {
        return builder.AddScheme<NoOpAuthOptions, NoOpAuthHandler>(authenticationScheme, displayName, configureOptions);
    }
}

And use it in your ConfigureServices method like this:

services
  .AddAuthentication(NoOpAuthHandler.NoOpSchema)
  .AddNoOpAuth();
RaphaelH
  • 1,894
  • 1
  • 27
  • 42
  • 1
    This worked for me. I'm not sure what `DefaultAuthenticateScheme` is actually for, but it's not for setting the default [Authorize] scheme. – King John Feb 07 '20 at 15:37
11

Make sure that you have Authentication middleware in your pipeline and place it before MVC.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ///

    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });
    ///
}

UPDATE

try using this code in HandleAuthenticateAsync method.

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    List<Claim> claims = new List<Claim>();
    ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, Scheme.Name);
    ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    AuthenticationTicket authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
    return AuthenticateResult.Success(authenticationTicket);
}
Kahbazi
  • 11,634
  • 1
  • 38
  • 62
  • Another note, in .NET Core 3/5, make sure you have `app.UseAuthentication` BEFORE `app.UseAuthorization()`. There goes 2 hours of my day :) – René Sackers Apr 09 '21 at 12:21
2

I suffer this for hours, in my case problem was default AuthenticationMiddleware class. More specifically; If you set ClaimsPrincipal within custom middleware in your request pipeline like below;

HttpContext.context.User = new ClaimsPrincipal(identity); this will override your default auth config settings,

What I did to solve; remove custom middleware and add app.UseAuthentication(); in Configure section in Startup.cs, and Authroize attribute checks whatever set in config section as default;

Here is mine;

services.AddAuthentication(x =>
            {
                x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateLifetime = true,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ClockSkew = TimeSpan.Zero
                };
            });
TyForHelpDude
  • 4,032
  • 8
  • 41
  • 76
1

in .NET Core 3.1, I had to manually add [Authorize(AuthenticationSchemes = "Bearer")] instead of a plain [Authorize] to endpoints in order for authentication to function as I'd expect. The following configuration change solved the issue:

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "Bearer";
    })
laaksom
  • 1,512
  • 1
  • 17
  • 16
0

try this.

services.AddAuthentication(
        options =>
        {
            options.DefaultAuthenticateScheme = "Cookie"
            options.DefaultChallengeScheme = Constants.NoOpSchema;
            options.DefaultSignInScheme = "Cookie";
        }
    )
    .AddCookie("Cookie")
    .AddScheme<CustomAuthOptions, CustomAuthHandler>(Constants.NoOpSchema, "Custom Auth", o => { });
Oleg
  • 107
  • 1
  • 10