Asked by:
How to handle external authentication when OWIN middleware and client are different hosts?

Question
-
User809845054 posted
Hi,
I'm trying to develop WEB API for mobile and regular site with some common functionality, one of which is authentication service. I take server side part of VS2013 SPA template and trying to implement security based on individual accounts. The main requirement is ability to login with username/password and login via facebook. Tempate for VS2013 works fine when OWIN middleware and client - in the same project (like in example with VS2013 SPA), but when it's not there some revision should be made which I want to share:
1. On WEB API which serves as OWIN middleware CORS should be enabled.
2. In UI client (which is separate project/solution) for each AJAX request should be added additional header "X-Requested-With": "XMLHttpRequest". I noticed that without it WEB API returns XML and not JSON payload which cause incorrect mappings in UI samples (failJSON can't parse error messages).
3. UI examples in VS2013 SPA template set for using with the same host, so some prefix with base url (WEB API url) should be added for each request in UI client.So I have successfully extended solution by implementing custom UserManager, UserStore, IIdentityValidator and IPasswordHasher which works with my existing MongoDB database and custom logic. Username/password authentication works just fine - I receive bearer token from WEB API and can use it in subsequent calls. But the main problem I have with external authentication: in my case with facebook. Actually all external login flow goes well and predicted till last step which is:
The authorization endpoint checks the external sign in cookie principal and finds the associated application user, then signs in the user as Bearer authentication type into the authorization server middleware. Since the authorization server sees that the request parameter response_type is token (in step 1), it will trigger implicit flow, which will create access the token and append it to the redirect_uri (step 1) as URL fragment. For example:
HTTP/1.1 302 Found
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Location: /#access_token=asd2342SDIUKJdsfjk3234&token_type=bearer&expires_in=1200&state=06hwltIjvnTn44hc
Set-Cookie: .AspNet.External=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: .AspNet.Cookies=WJgdyZQs9N8TG20EWnik-j0; path=/; HttpOnly
Content-Length: 0Here you can see /#access_token which is generated for WEB API host, full url looks like:
http://mobileapi.example.loc:61707/#access_token=zn73ihR7Uve3TNfTWNnB..AHHwOQijdOaeMDDccUIBbhPiEOVsGn2A&token_type=bearer&expires_in=1209600&state=jZbC35_Inoi8Zop22Mg7xyAzV-BUdE3Zj2k0bMcG5F81
So it redirects not to client which initiated authentication (for example http://localhost:2108), but on middleware host itself (http://mobileapi.example.loc:61707 in my case). Definitely it gives 404 Not Found because WEB API contains only server-side and can't parse this #access_token - UI client should do it.
So my question is how to correctly intercept redirection and substitute server host to client host?
P.S. I found source code which builds this #access_token string. It is in ApplyResponseGrantAsync method of OAuthAuthorizationServerHandler class, which instance created from OAuthAuthorizationServerMiddleware. And it has internal accessability. So it to change this behaviour I should rewrite as well whole middleware which is not very good approach I think.P.S.S. Anoter option I see is to create global handler on WEB API which intercepts all incoming requests and if it find one which starts from #access_token, than it takes refferer host from request and redirects to it. But it's ugly.
Thanx in advance.
Friday, March 28, 2014 8:42 AM
All replies
-
User1779161005 posted
Just change redirect_uri to the client's callback URL and then verify that URL in the OAuth2 authroization server middleware provider's OnValidateClientRedirectUri
Friday, March 28, 2014 9:51 AM -
User809845054 posted
Hi,
Just change redirect_uri to the client's callback URL and then verify that URL in the OAuth2 authroization server middleware provider's OnValidateClientRedirectUriPlease, explain where exactly should I change redirect_url to client's callback URL? Would be very appreciated if you can give code example
Friday, March 28, 2014 12:12 PM -
User1779161005 posted
This is Microsoft's docs on the subject:
http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
Friday, March 28, 2014 12:15 PM -
User809845054 posted
When I changing redirect_uri (in GetExternalLogins method on account controller) from:
http://mobileapi.example.loc:61707/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=mobile&redirect_uri=http%3A%2F%2Fmobileapi.example.loc%3A61707%2F&state=yWru4op036I0vgoCMgmE4CA5YqvjlB4aisT26id80yk1
which works, but has a redirect_uri set back to middleware which is http://mobileapi.example.loc:61707 (which I want to avoid)
to
http://mobileapi.example.loc:61707/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=mobile&redirect_uri=http%3A%2F%2Fmobile.example.loc%3A2108%2F&state=yWru4op036I0vgoCMgmE4CA5YqvjlB4aisT26id80yk1
which tries to set redirect_uri to http://mobile.example.loc:2108 (mobile web site) - it doesn't works and give me an error: error: invalid_request
What part of OAuthAuthorizationServer I should override to change this behavior and how?
Monday, March 31, 2014 5:45 AM -
User1813091986 posted
I struggled for days with this exact same issue. I know this post is old but if anyone else finds this especially if you are developing a non asp.net client e.g. mobile or single page app. In the ValidateClientRedirectUri method of ApplicationOAuthProvider change
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { if (context.ClientId == _publicClientId) { Uri expectedRootUri = new Uri(context.Request.Uri, "/"); if (expectedRootUri.AbsoluteUri == context.RedirectUri) { context.Validated(); } } return Task.FromResult<object>(null); }
to
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { if (context.ClientId == _publicClientId) { context.Validated(); } return Task.FromResult<object>(null); }
That way, whatever redirect url your application passes to the ExternalProvider action will be validated so that you can pass a different redirect for your web application and a different one for the mobile app. I hope this helps someone.
Sunday, February 22, 2015 4:29 PM -
User1222050094 posted
I've got a related issue, I've used this solution and it's getting me pretty close.
The returnUrl can set to anything but an Angular View, does anyone have an idea on how to get this to work?
'/api/account/externalLogins?returnUrl='+ encodeURIComponent('/user#/register-external') +'&generateState=true'
Wednesday, November 11, 2015 3:28 AM