22

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.

4
  • 1
    Have you add app.UseAuthentication() in your middleware pipeline? Commented Apr 19, 2018 at 8:39
  • what error do you get when you remove AuthenticationSchemes? Commented Apr 19, 2018 at 9:56
  • 1
    What does CustomAuthHandler do? Commented Apr 19, 2018 at 10:46
  • i supose you know, but you can inherit the authorize attribute to avoid the AuthenticationSchemes= Constants.NoOpSchema Commented Apr 19, 2018 at 14:21

5 Answers 5

20

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();
Sign up to request clarification or add additional context in comments.

2 Comments

This worked for me. I'm not sure what DefaultAuthenticateScheme is actually for, but it's not for setting the default [Authorize] scheme.
This worked for me, except I had to modify the NoOp HandleAuthenticateAsync provided by the question asker to return a GenericPrincipal instead of the User from the Context. Doesn't feel safe to be creating a GenericIdentity, but okay for my no-auth non-production use case.
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);
}

1 Comment

Another note, in .NET Core 3/5, make sure you have app.UseAuthentication BEFORE app.UseAuthorization(). There goes 2 hours of my day :)
3

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 Authorize 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
        };
    });

1 Comment

This answer helped me find that setting value of DefaultScheme does not work! What I needed was setting DefaultAuthenticateScheme to my scheme, so my custom authentication handler will be executed on every request weather controller action decorated with [Authorize(AuthenticationSchemes = MyAuthenticationConstants.Scheme)] or even [Authorize] attributes
2

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";
    })

Comments

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 => { });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.