Reputation: 123662
I have the following code, which worked fine under ASP.NET Core 2.2 (edited to remove product-specific naming)
public class Type1AuthenticationOptions : AuthenticationSchemeOptions {
public string Secret { get; set; }
}
public class Type1AuthenticationHandler : AuthenticationHandler<Type1AuthenticationOptions> {
public Type1AuthenticationHandler (IOptionsMonitor<Type1AuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
..
}
}
// Repeat for Type2AuthenticationHandler
My controllers were annotated thusly:
[Route("api/thing")]
[Authorize(AuthenticationSchemes = "type1")]
public class ThingController : Controller {
...
}
[Route("api/thing2")]
[Authorize(AuthenticationSchemes = "type2")] // different way of authorizing for thing2
public class Thing2Controller : Controller {
...
}
As above, this all worked great under Asp.NET Core 2.2 as well as prior version 2.1 and I think 2.0. The key parts (Configure and ConfigureServices are below)
Upon upgrade to Asp.NET Core 3.0, the code all still compiles - all the classes, attributes and structure still exist, however my Authentication Handler is never getting called. If I put a breakpoint in the constructor of Type1AuthenticationHandler
it doesn't even get hit.
My entire Configure() method method is as follows:
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
// Middleware to set the no-cache header value on ALL requests
app.Use((context, next) => {
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue { NoCache = true };
return next();
});
And my ConfigureServices is
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddControllers() // no views to be had here. Turn it off for security
.AddNewtonsoftJson(opts => {
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
opts.SerializerSettings.Converters.Add(new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() });
});
services
.AddAuthentication() // no default scheme, controllers must opt-in to authentication
.AddScheme<Type1AuthenticationOptions, Type1AuthenticationHandler>("type1", o =>
{
o.Secret = Configuration.GetThing1Secret();
})
.AddScheme<Type2AuthenticationOptions, Type2AuthenticationHandler>("type2", o =>
{
o.Secret = Configuration.GetThing2Secret();
});
services.AddAuthorization();
services.AddDbContext<DatabaseContext>(
options => options.UseNpgsql(Configuration.GetDatabaseConnectionString()),
ServiceLifetime.Transient);
// Used for fire and forget jobs that need access to the context outside of the
// the HTTP request that initiated the job
services.AddTransient(provider =>
new Func<DatabaseContext>(() => {
var options = new DbContextOptionsBuilder<DatabaseContext>()
.UseNpgsql(Configuration.GetDatabaseConnectionString())
.UseLoggerFactory(provider.GetRequiredService<ILoggerFactory>())
.Options;
return new DatabaseContext(options);
})
);
// make the configuration available to bits and pieces that may need it at runtime.
// Note: Generally it's bad practice to use this, you should configure your thing here, and inject the configured-thing instead
services.AddSingleton(Configuration);
services.AddMemoryCache();
// some more app-specific singleton services added but not relevant here
}
Any help much appreciated
P.S. on a long-shot, here's my csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UserSecretsId>99f957da-757d-4749-bd65-2b86753821a6</UserSecretsId>
<!-- SonarQube needs this but it's otherwise irrelevant for dotnet core -->
<ProjectGuid>{39054410-1375-4163-95e9-00f08b2efe2e}</ProjectGuid>
</PropertyGroup>
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.3.104.31" />
<PackageReference Include="AWSSDK.SimpleNotificationService" Version="3.3.101.68" />
<PackageReference Include="AWSSDK.SimpleSystemsManagement" Version="3.3.106.12" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0" PrivateAssets="all">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.3.100.1" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.101.50" />
<PackageReference Include="NLog" Version="4.6.7" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.0-preview9" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="System.IO.FileSystem.DriveInfo" Version="4.3.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
</ItemGroup>
</Project>
Upvotes: 3
Views: 4702
Reputation: 123662
After taking another more detailed look at the migration guide, I noticed this:
If the app uses authentication/authorization features such as AuthorizePage or [Authorize], place the call to UseAuthentication and UseAuthorization after UseRouting (and after UseCors if CORS Middleware is used).
The trick (which is not mentioned in the migration guide) appears to be that UseAuthentication/UseAuthorization needs to be AFTER UseRouting but BEFORE UseEndpoints.
This works now:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
If you place the Auth calls after UseEndpoints then you get an error about missing middleware
Upvotes: 16