I spoke to the aspnet IISIntegration team members and found my solution.
tldr: Empty out your wwwroot folder on kudu.
The issue relates to having old things leftover from previous 1.x deployments
Step 1:
Navigate to the Kudu Console (https://{yourapp}.scm.azurewebsites.net/)
Step 2:
Step 3:
(Note: navigate into the "site" directory)
(Note: there is a wwwroot folder within this wwwroot. You should delete the one that is in the "site" directory)
Step 4:
Add a new empty folder called wwwroot where you just deleted the previous one (within "site" directory)
(Note: my deployment failed when I didn't have the empty wwwroot folder in there)
Step 5: Redeploy your app and hopefully it works. Good luck
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();
}
}
Best Answer
the issue was actually coming from the fact that, at first, my web app was using .net core 1.1, which deploys all the DLL in the "wwwroot" folder of the web application. However, with asp.net core 2.0, it does not do that anymore as the DLL are picked up from a global store. However, as Visual Studio does not clean the destination folder before a publish, I ended up with a situation where the 1.1 DLL were in my wwwroot, so the web site was picking up these ones instead of the 2.0 ones in the store folder.
This is explained in more details here: https://github.com/Azure/app-service-announcements-discussions/issues/2#issuecomment-313816550