-
Notifications
You must be signed in to change notification settings - Fork 190
Auth API changes - Async only and add challenge behavior #323
Conversation
@@ -2,6 +2,7 @@ | |||
"version": "1.0.0-*", | |||
"description": "ASP.NET 5 HTTP object model. HttpContext and family.", | |||
"dependencies": { | |||
"Microsoft.AspNet.Http.Features": "1.0.0-*", |
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.
@kichalla Didn't you already add this?
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 : I did not check this in actually...i wanted to combine Session related changes with the checkin...
Would it be possible for someone to explain the reasoning behind changing to this style of API? Personally I find the new style a major regression from the point of view of clarity, readability and discovery. Instead of a function where we pass the Scheme to Authenticate() and get a result we now must construct a context object that will be mutated by the Authenticate method. This approach forces me to ask a ton of questions about API:
Functions/Methods have signatures for a reason. They have input parameters and they have return values. This allows to reason about the effects and non-effects of calling them. This use of context objects completely eliminates these benefits. Why are we doing it? |
Just to be clear, there still is the following API (which now directly gives you the principal):
Its only if you are using the less commonly accessed AuthenticationProperties/Description where you have to use the context |
@HaoK But that's syntactic sugar over the primary API which uses the context object, right? |
Depends on how far you want to take that line of argument, the whole AuthenticationManager API is 'sugar' on top of the HttpFeature which has always looked this way (AuthenticateContext)... The most commonly used API should be the new simpler ClaimsPrincipal Authenticate. We removed the intermediate API which returned a tuple of 3 things from the Context. The intent is to guide people to the new simplier API. |
I agree that the simpler ClaimsPrincipal Authenticate is better than the API that returned AuthenticationResult. And if the objective is to guide people towards the simpler API for the majority of cases then I would agree that is a good thing. |
@@ -117,6 +95,18 @@ public override void Challenge(string authenticationScheme, AuthenticationProper | |||
} | |||
} | |||
|
|||
// You are not allowed access | |||
public void Forbidden(string authenticationScheme, AuthenticationProperties properties) |
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.
Deny?
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 we changing the ChallengeBehavior enum to match the naming as well?
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.
Forbid?
I couldn't agree more with you, sir 👍 |
Renamed Forbidden => Forbid and added overloads to abstract base class to make it friendlier (optional AuthenticationProperties, and exposed the raw abstract Challenge overload which takes ChallengeBehavior which will make @lodejard happy) |
One thing I couldn't figure out how to do yet is remove the 401 setting inside of Challenge, @lodejard was adamant that this behavior belongs in the IAuthenticationHandler instead, I haven't quite gotten that to work yet. |
Per discussion with @Tratcher made AuthManager apis async to try and eliminate duplicate code paths in AuthHandlers. |
private static TResult RunSync<TResult>(Func<Task<TResult>> func) | ||
{ | ||
// REVIEW: Do we need to flow culture info still? | ||
return _myTaskFactory.StartNew(() => func()).Unwrap().GetAwaiter().GetResult(); |
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.
OMFG, what's that? 😱
It's bad, bad, bad, horribly bad: you're offloading an async delegate (() => func()
) to the thread pool before unwrapping it and then blocking on this new task 😄
This clearly creates unnecessary pressure on the thread pool, specially since you use StartNew
on an Task
-returning delegate, which is supposed to be non-blocking.
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.
I'd be really surprised if this was still necessary now that we no longer have the System.Web call context.
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.
Ok cool, so its definitely not as gross with out this...
@davidfowl What do you think about this change? (Making everything async only for auth, and only having a sync public wrapper in the manager?
@@ -61,7 +61,7 @@ public OwinEnvironment(HttpContext context) | |||
{ OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) }, | |||
{ OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) }, | |||
{ OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) }, | |||
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Action<object>, object>(feature.OnSendingHeaders)) }, | |||
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Func<object, Task>, object>(feature.OnResponseStarting)) }, |
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'm not sure you can change the signature of server.OnSendingHeaders
without breaking tons of OWIN middleware that expect a Action<Action<object>, object>
.
} | ||
|
||
public abstract void Challenge(string authenticationScheme, AuthenticationProperties properties); | ||
// Leave it up to authentication handler to do the right thing for the challenge | ||
public Task ChallengeAsync([NotNull] string authenticationScheme, AuthenticationProperties properties) |
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.
You're using [NotNull]
here but then passing in null from the overloads. This can only end well...
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.
Updated PR to use [NotNull] everywhere, Challenge sugar methods now pass string.Empty since we were already doing string.IsNullOrWhitespace checks, this shouldn't affect anything. Also nuked the overloads of SignIn/SignOut that didn't require a scheme, this was on the todo list from earlier API reviews with Lou
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.
IMHO, using string.Empty
is a bit ugly. And removing SignIn/SignOut
overloads that don't take a specific scheme while keeping the Challenge
method that takes no parameter is a bit weird, no?
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.
Naked Challenge is required for naked Authorize which is pretty important for MVC.
SignIn/SignOut with no arguments where any automatic middleware picks that up felt like a pretty unnecessary feature.
We are going to try and make it explicit so authenticationScheme is more understood since its always 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.
Well, SignIn/SignOut with no arguments
are a great way for the underlying application to keep the separation of concerns clear. For instance, controllers shouldn't have to know which schemes have been configured in Startup.cs request to globally sign in or sign out the user.
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.
That's what identity is for. The feedback has been pretty consistent that AutomaticAuthentication is confusing. We need it for naked challenge and the automatic cookie/bearer flow. But for everything else we want to dial that back is our current thinking.
Added a new response.OnStarted/OnCompleted seems just as clear... I'm touching all of the signatures already, so if we want to rename these, its a free opportunity |
Yes, OnStarted/Completed is better. |
Updated with OnStarted/OnCompleted/OnCompetedDispose |
} | ||
public abstract void OnCompleted(Func<object, Task> callback, object state); | ||
|
||
public virtual void OnCompletedDispose(IDisposable disposable) => OnStarting(_disposeDelegate, disposable); |
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 should be OnCompleted, not OnStarting.
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.
That would explain the functional failures I was seeing :)
Yes, that looks right. |
} | ||
public abstract void OnCompleted(Func<object, Task> callback, object state); | ||
|
||
public virtual void OnCompletedDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable); |
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.
[NotNull]
for everything but state
.
|
Changes needed for Authentication changes in security:
cc @lodejard @davidfowl @Tratcher @divega