My previous post regarding Migrating a .NET Core 1.1 application to 2.0 was a good primer for some of the direction in which .NET Core 2.0 would be going. However, that post was based on the 2.0 Preview, and there are breaking changes from Preview to RTM.
Authentication is significantly changed yet again!
Since writing this post, which was based on .NET Core 2.0 preview, Authentication is more consolidated in the 2.0 RTM. With .NET Core 2.0 final, all authentication is attached to the IServiceCollection’s extension “AddAuthentication.” Additionally, the “CookieBuilder” object was thrown into the mix to consolidate Cookie settings.
This change is referenced here: https://github.com/aspnet/Announcements/issues/262
How does this look in code? It is nice and consolidated with a FluentAPI. Take a look at the below example which attaches Cookie and OAuth authentication.
// Add authentication services .AddAuthentication(o => { o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; o.DefaultChallengeScheme = oauthSettings.AuthType; }) .AddCookie(o => { o.LoginPath = new PathString(oauthSettings.LoginPath); o.LogoutPath = new PathString(oauthSettings.LogoutPath); o.Cookie = new CookieBuilder() { Name = oauthSettings.CookieName, Domain = string.IsNullOrWhiteSpace(oauthSettings.CookieDomain) ? null : oauthSettings.CookieDomain, Path = oauthSettings.CookiePath, Expiration = TimeSpan.FromMinutes(oauthSettings.ExpireMinutes), SecurePolicy = CookieSecurePolicy.Always, HttpOnly = true }; o.SlidingExpiration = oauthSettings.SlidingExpiration; o.ExpireTimeSpan = TimeSpan.FromMinutes(oauthSettings.ExpireMinutes); o.Events = new CookieAuthenticationEvents() { OnValidatePrincipal = async context => { await ValidateAsync(context); } }; o.TicketDataFormat = new TicketDataFormat(dataProtector); }) .AddOAuth(oauthSettings.AuthType, o => { // Note the sign-in scheme! o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; o.ClientId = oauthSettings.ClientId; o.ClientSecret = oauthSettings.ClientSecret; o.CallbackPath = new PathString(oauthSettings.CallbackPath); o.AuthorizationEndpoint = $"{oauthSettings.AuthServer}{oauthSettings.AuthPath}"; o.TokenEndpoint = $"{oauthSettings.AuthServer}{oauthSettings.AuthTokenPath}"; o.UserInformationEndpoint = $"{oauthSettings.AuthServer}/api/user"; o.Scope.Add("identity"); o.Scope.Add("roles"); // Handlers if we want them.. //o.Events = new OAuthEvents //{ // OnCreatingTicket = async context => { await CreateAuthTicket(context, oauthSettings); }, // OnTicketReceived = async context => { await TicketReceived(context); }, // OnRedirectToAuthorizationEndpoint = async context => { await RedirectToAuthorizationEndpoint(context); }, // OnRemoteFailure = async context => { await RequestFailed(context); } //}; o.BackchannelHttpHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { if (oauthSettings.ValidateCert && errors != System.Net.Security.SslPolicyErrors.None) { return false; } return true; } }; });
And, there is a new IApplicationBuilder extension that must be called in your Startup.cs too.
// We still have to use it after adding.. :/ app.UseAuthentication();
DataProtection
Injecting your own IXmlRepository to replace the default DataProtection IXmlRepository is quite different. I blogged about doing this in 1.1 with an Entity Framework based IXmlRepository, but that wasn’t working. It turns out that on top of the normal injection setup, we have to do this now:
// This is how the XMLRepository is injected now .. it's an option off of the KeyManagementOptions. Thanks Microsoft! //https://github.com/aspnet/DataProtection/blob/dev/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs builder.Services.AddScoped<IConfigureOptions<KeyManagementOptions>>(services => { return new ConfigureOptions<KeyManagementOptions>(options => { options.XmlRepository = services.GetService<IXmlRepository>(); }); });
This took me a while to sort out. Essentially, despite calling my extension methods that performed the appropriate injection the Core framework silently either ignored or didn’t acknowledge my settings. Had I not been looking at the startup logs, I don’t think I would have noticed until things failed after deployment.