Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Add RemoteAuthenticationHandler base (support for error handling) #495

Closed
wants to merge 27 commits into from

Conversation

HaoK
Copy link
Member

@HaoK HaoK commented Sep 25, 2015

@dnfclas
Copy link

dnfclas commented Sep 25, 2015

Hi @HaoK, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by .NET Foundation and real humans are currently evaluating your PR.

TTYL, DNFBOT;

@HaoK
Copy link
Member Author

HaoK commented Sep 25, 2015

Not sure where best to integrate this with ODIC yet. Also still need to see if this makes sense in Cookies anywhere

/// The request path within the application's base path where the user-agent will be returned
/// when a failure occurs.
/// </summary>
public PathString ErrorHandlerPath { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this property is general enough to put in the base AuthenticationOptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where else could it live if we want the common error handler logic in the base handler?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is: do we want to have that in the base handler? This error handling is only applicable to authentication handlers implementing InvokeAsync (since you can't control the flow elsewhere) and I'm not sure all middleware will implement it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah well the other option is to introduce a base class for all of the Invoke style middlewares (not cookie/bearer) basically. I've been leaning towards that anyways, I'm not opposed to taking a stab at that and moving this error handling into the new common base. We need to rename InvokeAsync anyways, that name is terrible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps RedirectingAuthenticationMiddleware as the name of the new shared base class for OAuth/OIDC/Twitter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean exactly by "common flows"? It sounds like a crazy idea 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you and @Tratcher both think I'm crazy :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PinpointTownes see new RemoteAuthenticationHandler shared base class, @Tratcher has grudgingly come around to its value... standardizes Invoke path for OAuth/Twitter/OIDC

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed anymore, correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep nuking

@HaoK
Copy link
Member Author

HaoK commented Sep 29, 2015

Updated with major changes:

@HaoK
Copy link
Member Author

HaoK commented Sep 30, 2015

Updated with a stab at moving InvokeReplyPath to a new RemoteAuthenticationHandler base for OAuth and Twitter, I also sucked some of the common options into here as well like (BackChannel/CallbackPath). I also moved SigningIn to this base class/options, but I'm not even sure we really need this event anymore...

}

if (authenticationFailedContext.Skipped)
{
return null;
return new AuthenticateResult();
}

throw;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to re-throwing exceptions by default at this stage. But they MUST be caught somewhere: we can't afford returning a 500 response for a simple invalid/expired token.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure we don't forget about this. Once the higher level changes are agreed on, the fun process of going through each of the specific error/exception paths needs to be reviewed :/

@HaoK
Copy link
Member Author

HaoK commented Oct 2, 2015

New iteration changes:
RemoteAuthorizationOptions is now the base class for OAuth/Twitter/OpenIdConnectOptions. It has a separate IRemoteEvents property which contains the Error and SigningIn events which are only invoked via the newly renamed HandleRemoteCallbackAsync. The old handler specific events are unchanged and continue to live in their stand alone Events property.

HandleErrorAsync is a no-op in the base AuthHandler, and overridden in RemoteHandler to invoke the Error event on RemoteOptions.RemoteEvents.

@HaoK HaoK force-pushed the haok9-25/errors branch from e69f8d2 to 21dd52c Compare October 9, 2015 01:15
@HaoK
Copy link
Member Author

HaoK commented Oct 9, 2015

Update:

  • Only RemoteAuthenticationHandler/Invoke code paths use and inspect the ErrorContext.
  • No events for TicketReceived/RemoteError, as that would pose issues with the derived handlers having to cast, or using new. (I think we should just nuke all of the event classes and switch to simple delegates for each on the options directly).
  • Nuked generics off of the base context types, instead each group (OAuth/Twitter/OIDC/Cookies) defines the correctly typed TOption lower down in their respective places.
  • OAuth/OIDC now return error authentication results instead of null tickets
  • Convenience methods of AuthenticationResult.Success/Failed

TBD: Renaming OIDC events, and adding control flow to cookies to see if BaseContext can be merged with BaseControlContext

@HaoK HaoK changed the title First stab at error handling Add RemoteAuthenticationHandler base (support for error handling) Oct 9, 2015
@kevinchalet
Copy link
Contributor

Nice 😄

@PinpointTownes see new RemoteAuthenticationHandler shared base class, @Tratcher has grudgingly come around to its value... standardizes Invoke path for OAuth/Twitter/OIDC

Actually, it doesn't look that bad but... 😄

No events for TicketReceived/RemoteError, as that would pose issues with the derived handlers having to cast, or using new. (I think we should just nuke all of the event classes and switch to simple delegates for each on the options directly).

Outch, it's a horrible pattern that will kill hundreds of poor kittens 😢
If this is the price to pay for having a new RemoteAuthenticationHandler, I'm not sure it's really worth it: events are for advanced flow control. Merging them with the options would create too much noise and make the real events harder to discover.

Nuked generics off of the base context types, instead each group (OAuth/Twitter/OIDC/Cookies) defines the correctly typed TOption lower down in their respective places.

Curious: what's the benefit? 😮

Convenience methods of AuthenticationResult.Success/Failed

Oh yeah, it's much nicer 👍

That said, I know wonder why we need this class at all, since it simply exposes the ticket and an exception... why not simply returning the ticket and throwing exceptions? 😄
Also, I'm not sure how I feel when I see one of the Failed methods creating an exception on-the-fly without ever throwing it.

@@ -33,6 +33,8 @@ public class CookieSignedInContext : BaseContext<CookieAuthenticationOptions>
Properties = properties;
}

public new CookieAuthenticationOptions Options { get { return (CookieAuthenticationOptions)base.Options; } }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover turds from previous iterations, nuking thanks :)

@HaoK
Copy link
Member Author

HaoK commented Oct 9, 2015

So the other option which I had previously was keeping the base remote events in a separate IRemoteEvents property on the base options so they don't interfere with the actual provider events.

options.RemoteEvents = new RemoteEvents() { OnError = ... }; (note I've come around fully to @Tratcher's view that the events should just be simple delegates without methods/interface)

@HaoK
Copy link
Member Author

HaoK commented Oct 9, 2015

Updated: AuthenticationResult.Sucess to throw if a null ticket is provided, so the only time Ticket should be null is via the Failed method

}
}

// REVIEW: this maybe return an error instead?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@HaoK
Copy link
Member Author

HaoK commented Oct 9, 2015

Introduced new protected virtual HandleRemoteAuthenticateAsync to RemoteAuthenticationHandler and moved all the Invoke path logic there (old HandleAuthenticate method now returns a failed authenticated result). We should consider if its more appropriate to use a no-op successful empty ticket like before

@HaoK
Copy link
Member Author

HaoK commented Oct 12, 2015

@Tratcher Updated with new iteration based on what we decided on:

Put events back into the base Event property and use it as a new base

/// <summary>
/// </summary>
/// <param name="context"></param>
/// <returns>True if no other handlers should be called</returns>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty docs, doesn't return anything.

@HaoK
Copy link
Member Author

HaoK commented Oct 14, 2015

Updated with the new iteration:

  • Split AutomaticAuthentication into AutomaticChallenge / AutomaticAuthenticate
  • Brought back bool control flow for Unauthorized/Challenge
  • OAuth throws with no catch which is a temporary behavior change until we revisit error handling in the next PR

@Tratcher

@@ -29,7 +29,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)

app.UseCookieAuthentication(options =>
{
options.AutomaticAuthentication = true;
options.AutomaticAuthenticate = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tratcher
Copy link
Member

Almost done ⌚


namespace Microsoft.AspNet.Authentication.Cookies
{
public class BaseCookieContext : BaseContext
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious: why do you need this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We nuked Options/Generics off of the base contexts, and just added them all back in the specific OAuth/Cookies/Facebook contexts themselves.

        public CookieAuthenticationOptions Options { get; }

@HaoK
Copy link
Member Author

HaoK commented Oct 15, 2015

Updated, any last requests? I'll push this tomorrow in the afternoon, MVC/Identity changes look to be fairly small surprisingly :)

@HaoK
Copy link
Member Author

HaoK commented Oct 15, 2015

409b502 will address any remaining issues in next iteration/PR

@HaoK HaoK closed this Oct 15, 2015
return false;
}

if (context.Principal != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still trying to understand how you would get to this point without a principal or ReturnUri. I would expect all of those cases to be error conditions handled above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event can simply null out the principal or return uri, its settable on the context today

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would mean you're using the context property values as a form of flow control. Since we have real flow control now I don't think we should support the old hacky approach.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the context properties read only for Ticket/Principal then? I'm fine with that :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we still want to allow people to replace it, but I don't think we should support null values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is we allow AuthenticationTickets with null principals. So we can't easily block this unless we change that.

@HaoK HaoK deleted the haok9-25/errors branch February 23, 2016 20:39
@eriksendc
Copy link

Hi All,

Before posting a new issue I wanted to see if perhaps this issue was about or related to an issue I'm having. I'm on RC1, When a user selects to authenticate w/Facebook, and they put in their user name and password correctly, but clicks the Cancel button on the "this is what the app is requesting" confirmation, then the user's browser is redirected to

https://localhost:44300/signin-facebook?error=access_denied&error_code=200&error_description=Permissions+error&error_reason=user_denied&state=CfDJ8BCOQ9uBPU9Cu4ITtOhOeD-LHOt2vhRmJu5_xD4rvL3zm4ka-ak_iYgNuIJOOjjMsO1Fg5p5Tb8Fll6CJ064ysTkRrC46xaeG_vu5sFQdpPKjjGPokfsPnndPsB84CnDmRIzjwLeGgYzbESqjjGyAFUmSACDAYLo9LmmA8HJNPcdJIxTvcrepdr0GfE2DhJtP_D0Z7wNxyzJw1Jf8pGprHP8P4Q130hpmFzOLA2UCwjXYPxijlOP9LpY6Pgz66PKVJt_nO-6e0yA7aStVJTCOH8ic-I2xRguFAWXpfoim8wlqaizDupXFBBgeBtw9RvopkxKENby2N89okirRZSFK6FTLZJaG7RKxEWv_aUZskoFKmrKrkZDm0Ur3DyWnH53fQ#_=_

Control doesn't seem to be flowing back to my ASP.NET web application (I never get back anything in any /Account/xxx account controller method.

Wanted to report as an issue if it isn't one yet, but didn't want to add a new issue if this has already been fixed.

Thanks,
-Brian Eriksen

@kevinchalet
Copy link
Contributor

@eriksendc hey,

There's actually a ticket tracking that: #710.

@eriksendc
Copy link

Thx!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants