Multiple Authentication Schemes ASPNET Core 3 - c#

See Update below
I'm using Azure AD B2C and I'd like my users to be able to log in thru my web app as well as be able to utilize JWT bearer tokens and call Web API methods from a mobile app.
I can get either authentication scheme to work by itself. For example, in my startup.cs I can do the following:
services
.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
which works as expected (a user can login on the web site, but JWT doesn't work).
Alternatively, I can instead use the following and then only JWT bearer tokens will work:
services
.AddAuthentication(AzureADB2CDefaults.JwtBearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
If I want either to work, I can do the following (with the help of https://stackoverflow.com/a/49706390)
services
.AddAuthentication()
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options))
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(AzureADB2CDefaults.AuthenticationScheme, AzureADB2CDefaults.JwtBearerAuthenticationScheme)
.Build();
}
And now either will work. (edit: actually, they don't work completely)
HOWEVER, I also have this code:
app.UseAuthentication();
app.UseMiddleware<MyAfterAuthenticatedMiddleware>();
app.UseAuthorization();
The problem is that when I use the combination of either authentication, when my middleware code runs, my user is not authenticated (in the middleware code) and has no claims, etc. but obtains them later in the pipeline.
What's happening here? And how can this be fixed?
It seems that when I don't specify a default authentication scheme--in order to have multiple schemes--the authentication is not happening until the authorization step in the pipeline.
I need my middleware to run after authentication and before authorization.
How can I make that happen with the multiple authentication schemes?
UPDATE -- Solved! But there must be a better way!??
First of all, to the people who have created the .NET security stuff, I say kudos. It's important and it's difficult. However I do think there may be a lot of room for improvement.
Most developers dabble in security when they have to, and then go back to their "regular" job". Unless you work with it every day, it's tough to keep on top of. Every time you go back to it, everything's changed yet again.
It must be a common scenario: I want my users to be able to log in to my web site and interact with various web API methods. I would like them to also be able to access those same API methods via another means, such as a mobile app--where I'd be using JWT tokens, or equivalent.
This shouldn't be hard to make work.
However, I was tying myself into knots creating handlers for this and policies for that. One thing would work, but another thing wouldn't. Then when I thought things were right--I discovered some of the challenge and forbid logic didn't work as expected.
The built-in Authorization middleware has the ability to do authentication -- this was one of the early roads I went down, only to discover that it didn't fully work -- and it caused other problems for me, as described above.
In my opinion, authentication should not happen during authorization. Authentication should happen where it is expected--in authentication middleware. (My guess is that it was added in authorization in order to work around some other problem that presented itself years ago -- and perhaps still exists today)
Anyway--here is how I finally got things to work. It could be a lot cleaner and slicker and more flexible, but it works for my needs. And it is less of a hack than anything else I have seen. But is there a nicer, built-in class that could have done this for me?
My new question is this: is there a better way to get this done than how I've done it as described below?. It's hard to believe this is the best way.
In Startup.ConfigureServices I have now have the following:
services
.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options))
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
I then also have:
services.AddHttpContextAccessor();
services.AddSingleton<IAuthenticationSchemeProvider, MyAuthenticationSchemeProvider>();
And finally, I have a new class:
public class MyAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
public MyAuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
HttpContextAccessor = httpContextAccessor;
}
protected MyAuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes, IHttpContextAccessor httpContextAccessor) : base(options, schemes)
{
HttpContextAccessor = httpContextAccessor;
}
private IHttpContextAccessor HttpContextAccessor { get; }
private bool IsBearerRequest()
{
var httpContext = HttpContextAccessor.HttpContext;
return httpContext.Request.Headers.ContainsKey("Authorization")
&& httpContext.Request.Headers["Authorization"].Any(x => x.ToLower().Contains("bearer"));
}
public async Task<AuthenticationScheme> GetMySchemeAsync()
{
return IsBearerRequest()
? await GetSchemeAsync(AzureADB2CDefaults.BearerAuthenticationScheme)
: await base.GetDefaultAuthenticateSchemeAsync();
}
public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
{
return await GetMySchemeAsync();
}
public override Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
{
return GetMySchemeAsync();
}
public override Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
{
return GetMySchemeAsync();
}
}
Now I can use both kinds of authentication, the challenge & forbid work as expected. Why isn't there a built-in class that allows for switching between authentication schemes? Why does the authorization middleware attempt to authenticate with multiple schemes (I say it shouldn't do it at all), but not the authentication middleware?
Now this is here for anyone else who struggles with a similar issue.

Related

How to configure JWT for ASP.NET API containing public razor pages and health checks

I have a hard time configuring the API authentication and authorization. There is definitely something I do not understand. Ideas appreciated.
I have NET Core 3.1 API. It contains Razor Pages (documentation and Swagger UI).
There is no sign-in endpoint. The token is validated using the secret signing key.
Requirements:
Use JWT
Make all endpoints protected by default.
Razor pages are public.
Enable anonymous access to health checks.
Enable anonymous access to several endpoints.
Support roles.
I am not sure how to combine all the settings to make it work properly.
I've implemented a custom authentication handler.
I've tried many combinations, add/removing parts of the config.
The main problem is the handler is called every time (AllowAnonymous attribute is ignored) or I'm getting 401 instead of 403.
Playing with:
// in controllers
[Authorize(Roles="Role")]
[AllowAnonymous]
// in configure services
services.AddRazorPages(options => options.Conventions.AllowAnonymousToFolder("/"));
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddScheme<JwtBearerOptions, JwtBearerAuthenticationHandler>(JwtBearerDefaults.AuthenticationScheme, options => { });
services.AddAuthorization(options =>
{
// expected to add the default schema for the [Authorize] attribute (so I do not need to write it explicitly)
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
// tried to resolve the health checks call the authentication handler
options.AddPolicy("PublicPolicy", new AuthorizationPolicyBuilder()
.RequireAssertion(_ => true)
.Build());
});
// in configure
app.UseAuthentication(); // will cause all actions like decoreted with [Authorize]
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages().RequireAuthorization(SecurityRegistration.PublicPolicyName);
// map health checks
endpoints.MapApiHealthChecks("/health")
.RequireAuthorization("PublicPolicy");
}
I have implemented a custom authentication handler. Later on, I refactored it and inherited the JwtBearerHandler.
public class JwtBearerAuthenticationHandler: JwtBearerHandler
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
}
}
Why not using the AddJwtBearer()? I am generating/validating the tokes and I need to implement the OptionsMonitor pattern (reading the security settings from JSON, namely signing key, issuer, audience and expiration).
I expected that when I decorate a controller or an action with [AllowAnonymous] attribute, the handler will not be invoked. But it is called every time.
When I remove the UseAuthentication() I am getting issues with the default authentication schema and I'm getting 401 instead of 403.
I've also tried to add/remove the authorize attribute to controllers using a filter with/without a policy parameter.
configure.Filters.Add(new AuthorizeFilter());

Authentication/Authorization with ASP .NET Core and React based on Identity

I've been trying to create a SPA web application using React with ASP .NET Core 3.1 as backend, and now I need to restrict users going to certain pages. I know that for API methods I can do the following:
[Authorize(Roles="admin")]
[HttpGet]
public async Task<Whatever> Get(){ ... }
But that would only block users from using the API methods, which is good, but I also want to not let them go into the pages themselves.
Since I do not want to use their Blazor pages (because it breaks the separation of the client app and the backend), I cannot scaffold their login page, so I created mine using React and then implemented login and logout methods, which work, since when I'm logged in, the AspNetCore.Identity.Application cookie appears.
In order to do this, I added the following lines to Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentityCore<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.AddAuthentication (o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
}).AddIdentityCookies(o => {});
...
}
public void Configure(IApplicationBuilder app)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
Now I've read (source: https://stackoverflow.com/a/40055744/14806778) that, in React, you can define an onEnter method and check authentication there. So, to check if user is logged in or not, I implemented the simple method:
public bool IsUserLoggedIn()
{
return User.Identity.IsAuthenticated;
}
This also works, so I guess I could call this method in the onEnter method of React routing. I don't know if that's efficient or not, though.
Last, to check if user is in role, I could do something like this:
public async Task<bool> IsUserInRole(string requiredRole)
{
if(User.Identity.IsAuthenticated)
{
var user = await _userManager.GetUserAsync(User);
return await _userManager.IsInRoleAsync(user, requiredRole);
}
return false;
}
And call this onEnter instead.
My question is, is this approach valid? Is it safe? Does it have a huge performance impact? What are the alternatives? I've looked around a bit but I haven't seen a lot. I've read about JWT but I don't know how is it so different to this.
Also, I don't need Google/Apple/Facebook login, this is for an app which will be most likely running on localhost inside a VPN. I am using .NET Core 3.1, React 16.14 and React router dom 5.2.0.
Thank you.

.NET Core route based authentication with multiple B2C environments

Situation
We have clients that should be able to login into our application. Our clients do also have clients, who also may login. Therefore we have an Azure AD B2C environment per client.
So, we want to have one single application that can be used to authenticate against multiple Azure B2C environments. We want to have this route-based. So:
/client1 goes to B2C environment Client1B2C, with user flow B2C_1_Client1
/client2 goes to B2C environment Client2B2C, with user flow B2C_1_Client2
Challenge
So, we need to define multiple instances of AddOpenIdConnect. I do this inside a specific builder, so my Startup.cs keeps clean:
Startup.cs
...
var AzureAdB2CSettings = new List<AzureAdB2COptions>();
Configuration.GetSection("Authentication:AzureAdB2C").Bind(AzureAdB2CSettings, c => c.BindNonPublicProperties = true);
services.AddAuthentication(sharedOptions =>
{
...
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options), AzureAdB2CSettings)
...
And there is the builder:
AzureAdB2CAuthenticationBuilderExtensions.cs
...
public static string policyToUse;
public static AuthenticationBuilder AddAzureAdB2C(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions, List<AzureAdB2COptions> openIdOptions)
{
...
foreach(var b2c in openIdOptions)
{
builder.AddOpenIdConnect(b2c.SignUpSignInPolicyId, b2c.SignUpSignInPolicyId, options =>
{
options.Authority = b2c.Authority;
options.ClientId = b2c.ClientId;
options.CallbackPath = b2c.CallbackPath;
options.SignedOutCallbackPath = b2c.SignedOutCallbackPath;
options.ClientSecret = b2c.ClientSecret;
});
}
return builder;
}
...
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
...
string policyToUse = "B2C_1_" + context.Request.Query["area"];
...
var b2cSettings = AzureAdB2CSettings.Find(x => x.SignUpSignInPolicyId.ToLower().Equals(policyToUse.ToLower()));
AzureAdB2CAuthenticationBuilderExtensions.policyToUse = b2cSettings.DefaultPolicy;
...
Yippee ya yeeey! We can have a dynamic amount of add AddOpenIdConnect, based on a configuration file. The chosen authentication scheme has been set to the static string "AzureAdB2CAuthenticationBuilderExtension.policyToUse".
But now it comes... how to define the Authorization header?
BackofficeController.cs
...
[Authorize(AuthenticationSchemes = AzureAdB2CAuthenticationBuilderExtensions.policyToUse)]
public async Task<IActionResult> ChooseBackoffice()
{
...
}
...
AUTCH!! You can't use dynamic attributes... Have tried to set the chosen scheme as a default, but it seems we can only define a default at startup, not during runtime...
Any suggestions how to solve this challenge?
One suggestion is to set all possible values of AzureAdB2CAuthenticationBuilderExtensions.policyToUse in config and read from there.
For each action method/controller (as per your use case), define the attribute value from these configs.
It seems indeed impossible at the moment to have multiple B2C environments connected to one Azure App Service.
Therefore there is a choice:
Don't do it. Just create one giant B2C environment.
Make a multi-instance application instead of a multi-tenant application.
Our partner came with another solution. We haven't explored this route. Who knows does this help somebody:
Orchard core. Seems like a multi-tenant .NET Core solution. Looks like a complete application, where this multi-tenant question will be handled.
We did choose option 2. This makes sure we have a good separation of data. There are more hosting costs, although with a multi-tenant application all the traffic does to one application. This does require better hardware, so is also more expensive. I do not know which option is more expensive.
Now comes the question how to deploy this efficiently, but that's another question...

How do I disable/enable authentication at runtime in Asp.Net Core 2.2?

A website is per default anonymous access only.
The admin has a button to switch the site into maintenance mode, which should enable authorization using the built-in CookieAuthentication (flip a bit in a database, not relevant for this post).
In order to make that work, I first configured cookie authentication (in startup.cs):
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/auth/login");
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
}
Then on relevant controllers, I put an [Authorize] attribute.
[Authorize]
public class HomeController : Controller
{
//removed
}
This works perfectly - cookie auth kicks in when authorize-attribute is present. So far so good.
Now I want to disable authorization at runtime when maintenance mode is off.
Attempted Solution
This is what I ended up with after a lot of trial and error and research.
public void OnAuthorization(AuthorizationFilterContext context)
{
IMaintenanceModeDataService ds = context.HttpContext.RequestServices.GetService<IMaintenanceModeDataService>();
if (!ds.IsMaintenanceModeEnabled)
{
//Maintenance mode is off, no need for authorization
return;
}
else
{
ClaimsPrincipal user = context.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
//when the user is authenticated, we don't need to do anything else.
return;
}
else
{
//we're in maintenance mode AND the user is not
//It is outside the scope of this to redirect to a login
//We just want to display maintenancemode.html
context.Result = new RedirectResult("/maintenancemode.html");
return;
}
}
}
[MaintenanceModeAwareAuthorize]
public class HomeController : Controller
{
//removed
}
This works great when the site is in maintenance mode.
When the site is NOT in maintenance mode, the cookie authentication still kicks in and requires auth. I could remove that and try to implement my own auth, but that would be stupid, when we already have perfectly well-crafted solutions built-in.
How do I disable authorization when the site is NOT in maintenance mode (at runtime)?
Notes:
Q: Why not handle this by doing x (which requires serverside access to config, environment vars, server or similar)?
A: Because this needs to be immediately accessible to non-technical admin-users by clicking a button in the backend.
Yes you can!
The authorization system in ASP.NET Core is extensible and you can implement your scenario easily with poliy-based authorization.
Two main things to know to get going:
an authorization policy is made of one or more requirements
all of the requirements must be satisfied for a policy to succeed
Our goal is then to create a requirement which is satisfied if any of the following statements is true:
the maintenance mode is not enabled, or
the user is authenticated
Let's see the code!
The first step is to create our requirement:
public class MaintenanceModeDisabledOrAuthenticatedUserRequirement : IAuthorizationRequirement
{
}
We then have to implement the handler for this requirement, which will determine if it's satisfied or not. The good news is handlers support dependency injection:
public class MaintenanceModeDisabledOrAuthenticatedUserRequirementHandler : AuthorizationHandler<MaintenanceModeDisabledOrAuthenticatedUserRequirement>
{
private readonly IMaintenanceModeDataService _maintenanceModeService;
public MaintenanceModeDisabledOrAuthenticatedUserRequirementHandler(IMaintenanceModeDataService maintenanceModeService)
{
_maintenanceModeService = maintenanceModeService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MaintenanceModeDisabledOrAuthenticatedUserRequirement requirement)
{
if (!_maintenanceModeService.IsMaintenanceModeEnabled || context.User.Identities.Any(x => x.IsAuthenticated))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Next, we need to create an authorization policy that uses this requirement, and you have 2 choices here:
you can redefine the default policy, used when "empty" [Authorize] attributes are used, or
create an explicit policy that you'll have to reference in your attributes, like [Authorize(Policy = "<your-policy-name>")]
There's no right or wrong answer; I'd pick the first option is my application had only one authorization policy, and the second one if it had several of them. We'll see how to do both:
services
.AddAuthorization(options =>
{
// 1. This is how you redefine the default policy
// By default, it requires the user to be authenticated
//
// See https://github.com/dotnet/aspnetcore/blob/30eec7d2ae99ad86cfd9fca8759bac0214de7b12/src/Security/Authorization/Core/src/AuthorizationOptions.cs#L22-L28
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement())
.Build();
// 2. Define a specific, named policy that you can reference from your [Authorize] attributes
options.AddPolicy("MaintenanceModeDisabledOrAuthenticatedUser", builder => builder
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement()));
});
Next, you need to register the requirement handler as an IAuthorizationHandler, as indicated in the official docs
// The lifetime you pick is up to you
// You just need to remember that it's got a dependency on IMaintenanceModeDataService, so if you
// registered the implementation of IMaintenanceModeDataService as a scoped service, you shouldn't
// register the handler as a singleton
// See this captive dependency article from Mark Seeman: https://blog.ploeh.dk/2014/06/02/captive-dependency/
services.AddScoped<IAuthorizationHandler, MaintenanceModeDisabledOrAuthenticatedUserRequirementHandler>();
The final step is to apply the [Authorize] attributes on your controllers/actions as needed.
// 1. If you redefined the default policy
[Authorize]
public IActionResult Index()
{
return View();
}
// 2. If you defined an explicit policy
[Authorize(Policy = "MaintenanceModeDisabledOrAuthenticatedUser")]
public IActionResult Index()
{
return View();
}
I am afraid that could not be done .The accept of authorization is different from authentication, when context.HttpContext.User.Identity.IsAuthenticated is false, it will always redirect to login page.
It's better to have actions that must or may require authorization in a controller together, and unauthorized actions in a separate controller with [AllowAnonymous].
if (!user.IsMaintenanceModeEnabled)
{
context.Result = new RedirectResult("Another controller with [AllowAnonymous]");
return;
}
Since current pages need work perfectly with anonymous mode, then authentication should NOT be in Controller level.
I think your requests are:
If a Maintancer login system,
run extra code to show maintance elements(switch button or others) on page, so Maintancer can switch page with different mode, and do maintancer actions
If user visit site anonymously, anonymous-mode elements will render to browser
If user login but not an Maintancer, normal-user-mode elements will render to browser
To resolve those, The key is to block unauthorized user to visit Maintancer ACTIONS, instead of controller.
my suggestions are:
in _Layout.cshtml page, check if Maintancer Login, then enject switch button
in the actions or pages that could visit anornymously, check if "Maintancer Login" && IsMaintenanceMode, then show Maintancer-authorized elements, like Delete Post, Edit Content, ...
in Controller.Actions that works only for Maintancer(like Delete Post), add [Authorize(Roles="Maintancer")] or [Authorize(Policy="Maintancer")] or you customized authorize.

ASP.NET Core 2.0 authentication middleware

With Core 1.1 followed #blowdart's advice and implemented a custom middleware:
https://stackoverflow.com/a/31465227/29821
It worked like this:
Middleware ran. Picked up a token from the request headers.
Verified the token and if valid built an identity (ClaimsIdentity) that contained multiple claims which then it added via HttpContext.User.AddIdentity();
In ConfigureServices using services.AddAuthorization I've added a policy to require the claim that is provided by the middleware.
In the controllers/actions I would then use [Authorize(Roles = "some role that the middleware added")]
This somewhat works with 2.0, except that if the token is not valid (step 2 above) and the claim is never added I get "No authenticationScheme was specified, and there was no DefaultChallengeScheme found."
So now I'm reading that auth changed in 2.0:
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x
What's the right path for me to do the same thing in ASP.NET Core 2.0? I don't see an example to do truly custom authentication.
So, after a long day of trying to solve this problem, I've finally figured out how Microsoft wants us to make custom authentication handlers for their new single-middleware setup in core 2.0.
After looking through some of the documentation on MSDN, I found a class called AuthenticationHandler<TOption> that implements the IAuthenticationHandler interface.
From there, I found an entire codebase with the existing authentication schemes located at https://github.com/aspnet/Security
Inside of one of these, it shows how Microsoft implements the JwtBearer authentication scheme. (https://github.com/aspnet/Security/tree/master/src/Microsoft.AspNetCore.Authentication.JwtBearer)
I copied most of that code over into a new folder, and cleared out all the things having to do with JwtBearer.
In the JwtBearerHandler class (which extends AuthenticationHandler<>), there's an override for Task<AuthenticateResult> HandleAuthenticateAsync()
I added in our old middleware for setting up claims through a custom token server, and was still encountering some issues with permissions, just spitting out a 200 OK instead of a 401 Unauthorized when a token was invalid and no claims were set up.
I realized that I had overridden Task HandleChallengeAsync(AuthenticationProperties properties) which for whatever reason is used to set permissions via [Authorize(Roles="")] in a controller.
After removing this override, the code had worked, and had successfully thrown a 401 when the permissions didn't match up.
The main takeaway from this is that now you can't use a custom middleware, you have to implement it via AuthenticationHandler<> and you have to set the DefaultAuthenticateScheme and DefaultChallengeScheme when using services.AddAuthentication(...).
Here's an example of what this should all look like:
In Startup.cs / ConfigureServices() add:
services.AddAuthentication(options =>
{
// the scheme name has to match the value we're going to use in AuthenticationBuilder.AddScheme(...)
options.DefaultAuthenticateScheme = "Custom Scheme";
options.DefaultChallengeScheme = "Custom Scheme";
})
.AddCustomAuth(o => { });
In Startup.cs / Configure() add:
app.UseAuthentication();
Create a new file CustomAuthExtensions.cs
public static class CustomAuthExtensions
{
public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<CustomAuthOptions> configureOptions)
{
return builder.AddScheme<CustomAuthOptions, CustomAuthHandler>("Custom Scheme", "Custom Auth", configureOptions);
}
}
Create a new file CustomAuthOptions.cs
public class CustomAuthOptions: AuthenticationSchemeOptions
{
public CustomAuthOptions()
{
}
}
Create a new file CustomAuthHandler.cs
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
public CustomAuthHandler(IOptionsMonitor<CustomAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
// store custom services here...
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// build the claims and put them in "Context"; you need to import the Microsoft.AspNetCore.Authentication package
return AuthenticateResult.NoResult();
}
}
There are considerable changes in Identity from Core 1.x to Core 2.0 as the article you reference points out. The major change is getting away from the middleware approach and using dependency injection to configure custom services. This provides a lot more flexibility in customizing Identity for more complex implementations. So you want to get away from the middleware approach you mention above and move towards services. Follow the migration steps in the referenced article to achieve this goal. Start by replacing app.UseIdentity with app.UseAuthentication. UseIdentity is depreciated and will not be supported in future versions. For a complete example of how to insert a custom claims transformation and perform authorization on the claim view this blog post.

Categories

Resources