-
Notifications
You must be signed in to change notification settings - Fork 599
Authentication 2.0 Iteration 3 (with Names/OptionFactory) #1151
Conversation
@HaoK, |
@brockallen @PinpointTownes we've decided today that we are going ahead with this change, the implementation details are still subject to change, but the high level switch from middleware to services/DI/NamedOptions is a go. Definitely let us know if any of your important scenarios are affected, or if you have any ideas for quality of life improvements in the base auth handler classes, since the entire auth code base is opened up for surgery right now... |
Note to self: need to investigate adding schemes and configuring them outside of ConfigureServices. We can probably cheat and use the AuthenticationScheme.Settings bag to stuff options instances for this scenario |
Thanks for the info, I'll take a look ASAP. When do you plan to merge this PR? |
No firm ETA on merge, need to start working on the changes in all the other repos (Identity/servers), which likely will require some more changes here. But we can start settling on implementation choices now. Also the options changes will go in first, so I'd start there if you are interested on how that lands: aspnet/Options#176 |
SignOutScheme = SignInScheme; | ||
} | ||
|
||
if (Options.StateDataFormat == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HaoK correct me if I'm wrong, but Options
is a shared instance, right? (at least, when using the default options factory). So this means that the options could be modified concurrently, as InitializeAsync
is invoked per request. Isn't that risky as hell?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so that's why I'm trying to move all the Options manipulations into 'validations' that occur as part of OptionsFactory.Get("schemeName")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also switch to using the form of 'scoped' options for the schemes, IOptionsSnapshot for example which wouldn't be shared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Thanks for the clarification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think about it, that's probably the easiest fix to this issue since the scheme options don't want to share
/// The handler calls methods on the events which give the application control at certain points where processing is occurring. | ||
/// If it is not provided a default instance is supplied which does nothing when the methods are called. | ||
/// </summary> | ||
protected virtual object Events { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How bad would it be if AuthenticationHandler
had a TEvents
generic argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But yeah the events stuff needs some love, I guess every handler does have an events class today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The events is a bit weird to live on options, maybe we can move it into the AuthenticationScheme instead, as something that gets fed into Intialize(IOptionsFactory<TOptions>, TEvent events, HttpContext)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always felt configuring the events as part of the options makes it look ginormous. Can we do better if we tease it apart/have a new pattern for hooking events that's not tied to options configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure every event isn't overridable? Isn't that the only purpose for the IXyzEvents to exist? basically its the list of virtual methods that would exist if you could plug in your own handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure every event isn't overridable?
The events are all overridable, but not the infrastructure code that call them: the OAuthHandler.CreateTicketAsync
method you used in your sample is quite an exception, as it's the only handler that was really designed to be subclassed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So moving to a virtual method would allow us to nuke all of the context classes, and just pass them as parameters (or let them access handler directly since its now in the handler, which they couldn't even do before).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally like the fact the public hooks are separated from the rest of the infrastructure code, so I'd really love this events pattern to be present in 2.0.0, but heh, your PR, your choice 😄
(also, I'm not a huge fan of having to override a class to just to do something that can be done in a 2-line event delegate)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, @Tratcher had the same opinion, there's so many other things on my list right now, so its most likely not going to change soon
|
||
// Holds things like the configured options instances for the handler | ||
// Also replacement for AuthenticationDescription | ||
public IReadOnlyDictionary<string, object> Settings { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a weird property bag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this can go away now, since we can add/remove options dynamically I don't think this is needed at all. Fleshing out the add/remove auth scheme using custom options in the next update
throw new ArgumentException("handlerType must implement IAuthenticationSchemeHandler."); | ||
} | ||
HandlerType = handlerType ?? throw new ArgumentNullException(nameof(handlerType)); | ||
Settings = settings ?? throw new ArgumentNullException(nameof(settings)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax
public Type HandlerType { get; set; } | ||
|
||
// Holds things like the configured options instances for the handler | ||
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>(); // casing? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ordinal
AuthenticationScheme = authenticationScheme; | ||
Principal = principal; | ||
Principal = principal ?? throw new ArgumentNullException(nameof(principal)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax
Task SignOutAsync(SignOutContext context); | ||
} | ||
|
||
public interface IAuthenticationRequestHandler : IAuthenticationHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split file
|
||
namespace Microsoft.AspNetCore.Authentication | ||
{ | ||
public interface IAuthenticationSchemeProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did the implementation go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I nuked the old Authentication package and moved Impl into there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not in this PR anywhere.
Updated with some cleanup, nuked settings bag on AuthenticationScheme, RIP ?? throw |
So in terms of final homes: Authentication.Abstractions => Http Repo (keeps its own package) So... |
Which repos need to depend on Authentication.Core? |
samples/SocialSample/Startup.cs
Outdated
loggerfactory.AddConsole(LogLevel.Information); | ||
|
||
// Simple error page to avoid a repo dependency. | ||
app.Use(async (context, next) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just have Security depend on Diagnostics so we can stop doing this stuff? I don't think it creates any cycles, it just bumps security up the chart one level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems fine to me, I'll add it
samples/SocialSample/Startup.cs
Outdated
|
||
app.UseAuthentication(); | ||
|
||
if (string.IsNullOrEmpty(Configuration["facebook:appid"])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this check to ConfigureServices
|
||
namespace Microsoft.AspNetCore.Authentication | ||
{ | ||
public interface IAuthenticationSchemeProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not in this PR anywhere.
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param> | ||
/// <param name="options">A <see cref="CookieAuthenticationOptions"/> that specifies options for the middleware.</param> | ||
/// <returns>A reference to this instance after the operation has completed.</returns> | ||
public static IApplicationBuilder UseCookieAuthentication(this IApplicationBuilder app, CookieAuthenticationOptions options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bring these back as Obsolete Error with a fwlink.
public SignInContext(HttpContext context, string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties) | ||
: base(context, authenticationScheme, properties) | ||
{ | ||
Principal = principal ?? throw new ArgumentNullException(nameof(principal)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax
@@ -38,25 +56,20 @@ public class RemoteAuthenticationOptions : AuthenticationOptions | |||
/// Gets or sets the authentication scheme corresponding to the middleware | |||
/// responsible of persisting user's identity after a successful authentication. | |||
/// This value typically corresponds to a cookie middleware registered in the Startup class. | |||
/// When omitted, <see cref="SharedAuthenticationOptions.SignInScheme"/> is used as a fallback value. | |||
/// When omitted, <see cref="SignInScheme"/> is used as a fallback value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is now referencing itself?
using System.Reflection; | ||
using Microsoft.AspNetCore.Http; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tratcher they are here, but perhaps hard to find in the 178 file diff... maybe easier to just view via commits
@@ -71,8 +71,17 @@ protected AuthenticationHandler(IOptionsService<TOptions> optionsFactory, ILogge | |||
/// <returns></returns> | |||
public virtual Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) | |||
{ | |||
Scheme = scheme ?? throw new ArgumentNullException(nameof(scheme)); | |||
Context = context ?? throw new ArgumentNullException(nameof(context)); | |||
if (scheme == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tratcher See AuthenticationHandler here
@Tratcher I'll break this PR up into 2 new PRs to make things more managable, I'll start the PR for Abstractions/Core in HttpAbstractions and open a new one for only the Security changes |
Updated removing the stuff that is being moved to HttpAbstractions via: aspnet/HttpAbstractions#798 Hopefully easier to review |
@HaoK hum, is there a particular reason to move so many types to |
@PinpointTownes I think they're needed to write tests for HttpSysServer and IISIntegration |
|
The middleware isn't critical, I can move that back along with UseAuthentication to Security. What's critical is the core services/AddAuthenticationCore as you can't even use Auth without those. I'll move the Middleware/Use out now |
Updated, UseXyz's are back as obsolete/throw not Supported. Also brought back the AuthMiddleware from HttpAbstractions. |
@@ -12,26 +12,26 @@ namespace Microsoft.AspNetCore.Builder | |||
public static class CookieAppBuilderExtensions | |||
{ | |||
/// <summary> | |||
/// Obsolete, see TODO: fwdlink | |||
/// Obsolete, see https://go.microsoft.com/fwlink/?linkid=845470 | |||
/// </summary> | |||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param> | |||
/// <returns>A reference to this instance after the operation has completed.</returns> | |||
[Obsolete] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put the link in the Obsolete comment
@@ -16,7 +16,7 @@ public static class CookieAppBuilderExtensions | |||
/// </summary> | |||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param> | |||
/// <returns>A reference to this instance after the operation has completed.</returns> | |||
[Obsolete] | |||
[Obsolete("See https://go.microsoft.com/fwlink/?linkid=845470")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it's only a warning by default, you have to set IsError true
https://msdn.microsoft.com/en-us/library/system.obsoleteattribute.iserror(v=vs.110).aspx
Replaced with #1170 |
IOptionsFactory
which provides named options in addition to validation, both of which are triggered via IOptionsFactory.Get("name"). Allows normal options configuration for schemes, Validation is just a different set of Actions that run after the ConfiguresIOptionsFactory Usage:
IAuthenticationRequestHandler
interface instead, CallbackPaths routing is disabled for now (all IAuthenticationRequestHandler schemes are given a chance to handle the request)TL:DR Auth 2.0 summary
IAuthenticationService
with the sameAuthenticate/Challenge/SignIn/Out/ForbidAsync
API.AuthenticationScheme
instances which have: Name, Type, and Settings dictionary (Options instance + old description bag)app.UseXyzAuth(new XyzOptions())
=>services.AddXyzAuth(o => o.Foo = bar)
adds anAuthenticationScheme
instance for XyzUseAuthentication
is required, which takes care of the old 'automatic' authenticate, in addition to giving each scheme a chance to handle the request like the old middlewares did.Default[Authenticate/SignIn]Scheme
inAuthenticationOptions
, rather than a flag on each middleware. No ambiguity, altho there's one bit of magic where if none are specified and there's only a single scheme registered, we use that scheme as the default.Pros of the new design
IAuthenticationSchemeProvider
service.Example of using scoped DbContext
This was not really easily accomplished in the old stack since handlers were created by the middleware via
protected abstract AuthenticationHandler<TOptions> CreateHandler();
Claims Transformation
A lot simpler, new
IClaimsTransformation
service which has a single method:Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
We call this on any successful
AuthenticateAsync
call.To use a more advanced scoped claims transform that uses DbContext's, they would just need to add their own
IClaimsTransformation
before anyAddAuthentication
calls.