Monday, May 13, 2013

authentication - Cross-domain login using JSONP and cookies - Stack Overflow


We all know that cookies are not accessible cross-domain as this presents a security risk. However, using some trickery, there are ways around this. Basically we are setting a cookie for the user on a central domain, checking for that cookie's existence using a script, then using a JSON-P callback to copy that cookie onto the other domains. In more detail:

Logging In

Step 1
The 
 displayed on mydomain.com (or myotherdomain.com, etc) should POST tocentral.com/login
Step 2
On central.com/login, the username and password are verified and a cookie is set on thecentral.com domain containing a unique value for that user. The user is then redirected back tomydomain.com
SELECT unique_value FROM users WHERE username = $username
set cookie on central.com containing unique_value
Step 3
Back on mydomain.com we embed a script call to central.com/check.

Step 4
On central.com/check we check if the unique cookie is set for the user. Then we embed a JavaScript callback (JSON-P) that informs mydomain.com that the user is logged in. No sensitive user data is included, otherwise hacker.com could embed this script and get the user's information. Instead, we create a disposable hash based on the timestamp, so that mydomain.com can verify the authentication.
if cookie on central.com is valid
    user_data = array(
       'success' => true,
       'uid'     => $uid,
       'time'    => time_stamp,
       'hash'    => disposable_salted_hash( $uid, time_stamp )
    )
    echo 'setDomainCookie(' . json_encode(user_data) . ')'
Step 5
The callback function is then executed, setting the cookie on mydomain.com. Finally, we can either refresh the page or just alert the user using JavaScript that they are logged in (preferably both).
function setDomainCookie( user_data ) {
    if( user_data.success ) {
        $.post('/setcookie', user_data, function() {
            location.reload(true);
        }
    }
}
mydomain.com/setcookie is similar to Step 2. Of course this assumes both sites have access to the same database (and code)
if hash = disposable_salted_hash( $uid, time_stamp )
    SELECT unique_value FROM users WHERE uid = $uid
    set cookie on mydomain.com containing unique_value
Step 6
The next time the user refreshes the page, we can bypass the JSON-P callback
if cookie on mydomain.com is valid
    loggedin = true
else
    delete cookie on mydomain.com
    proceed to Step 3

Logging Out

Step 7
The link on mydomain.com should go to central.com/logout
Step 8
On central.com/logout, not only is the cookie deleted, but the unique value for that user is reset. The user is redirected back to mydomain.com
delete cookie on central.com
UPDATE users SET unique_value = new_random_value() WHERE username = $username
Step 9
Now that the unique value is reset, Step 6 from above fails, the cookie is also deleted frommydomain.com, and the user is effectively logged out.

Notes

  1. It is critical that central.com/check from Step 4 has the correct headers set so that it is not cached.
  2. Steps 3-5 when the user is logging in may cause a slight delay. It's wise to both refresh and show some kind of JavaScript alert that they are logged in. It's also important for the script from Step 3 to be as close to the top of the page as possible.
  3. In Step 5, you can optionally store a unique cookie value on each domain.
  4. The separate central.com domain is not really necessary; you can just use one of the other domains as the central domain if you wish. The logic for that domain would obviously be different.
  5. For this to work on Internet Explorer you will need a P3P policy attached to your cookies.
  6. Hope this is helpful to people. I'd be very interested to receive feedback, especially if there are any security flaws from this method. I think the worst a hacker could do is replicate Steps 3-5 and log you in to mydomain.com without you knowing, but that would be harmless.

No comments: