Question

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: 0

Here 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.

Was it helpful?

Solution

It much simplier than I thought. From client-side you just should set returnUrl to one you need. No changes on server-side.

For example I have function on client which calls get external logins:

function externalLoginsUrl(returnUrl, generateState) {
    return baseurl + "/api/Account/ExternalLogins?returnUrl=" + (encodeURIComponent(returnUrl)) +
        "&generateState=" + (generateState ? "true" : "false");
}

self.getExternalLogins = function (returnUrl, generateState) {
    return $.ajax(externalLoginsUrl(returnUrl, generateState), {
        cache: false,
        headers: getAllHeaders()
    });
};

where returnUrl:

self.returnUrl = window.location.origin;

That is all :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top