Looking at the OpenID landing page on the YDN site might give the impression that logging users into a site with a Yahoo! ID is difficult. Given the following conditions, however, adding Yahoo! login to your site can be quite simple:
- Because we’re only targeting Yahoo! accounts, we can preset the login location to https://open.login.yahooapis.com/openid/op/auth
- If we log users in via a popup, but manage everything via JavaScript on the parent page, we can skip validation of the response coming back from Yahoo!
Here’s an HTML stub to demonstrate all the code that’s required to add Yahoo! login to your site:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head></head> <body> <span id="login"></span> <script type="text/javascript" src="http://yui.yahooapis.com/3.2.0pr2/build/yui/yui-min.js"></script> <script type="text/javascript"src="http://gist.github.com/raw/619947/f344557a242fc987f95e7f5af3173a28ba94a58c/yui-login.js"></script> <script> YUI().use('login', function(Y) { Y.login.renderLoginButton('login'); }); </script> </body> </html>
You can run it here: http://example.erikeldridge.com/619947/demo.html
This code is maintained in a github gist: http://gist.github.com/619947
Here’s the YUI 3 module used in the code above. It provides simple login/logout buttons to perform the authentication flow. All it requires is an element on the page with an id. In the html above, the element has the id “login”. The id name is then passed into the Y.login.renderLoginButton method.
YUI.add('login', function(Y) { // This module assumes the openid flow takes place via a popup that cannot be accessed directly. // Given this condition, and to maintain simplicity, it doesn't attempt to verfiy the assertion. // If you modify this script to use a file that processes the assertion directly, you should verify the assertion. // @see http://openid.net/specs/openid-authentication-2_0.html#verification // Licensed under Yahoo! BSD // @see http://gist.github.com/375593 Y.namespace('login'); Y.login.cookieName = 'login_session'; Y.login.sessionReadyEventName = 'login:sessionReady'; // This function constructs an openid login url for Yahoo!, and opens a popup to this location // @param {string} returnUrl is the url to redirect to after the user goes through the login flow. Defaults to document.location.href Y.login.popup = function (returnUrl) { returnUrl = returnUrl || document.location.href; // @see http://openid.net/specs/openid-authentication-2_0.html#realms var realm = returnUrl.match(/(http[s]?:\/\/[^\/?]+)/)[0]; // Load openid login flow in popup window // @see http://developer.yahoo.com/openid/ var url = 'https://open.login.yahooapis.com/openid/op/auth?' + Y.QueryString.stringify({ 'openid.return_to': returnUrl, 'openid.mode': 'checkid_setup', 'openid.identity': 'http://specs.openid.net/auth/2.0/identifier_select', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.realm': realm, 'openid.claimed_id': 'http://specs.openid.net/auth/2.0/identifier_select' }); var name = 'login'; var params = 'toolbar=1,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1, width=500,height=500,left=200,top=200'; var popup = window.open(url, name, params); // Poll popup every 300 msec to see if it's redirected back to this domain // @see http://developer.yahoo.com/yui/3/api/YUI.html#method_later var timer = Y.later(300, Y, function() { // If user's closed window, stop timer if (popup.closed) { timer.cancel(); return; } // Wrap in try/catch to avoid fatal cross-domain exceptions try { // Openid's response is called the "assertion" // @see http://openid.net/specs/openid-authentication-2_0.html#positive_assertions var assertion = popup.location.href.split('?')[1]; assertion = Y.QueryString.parse(assertion); // Extract the user-specific info var session = { 'username': assertion['openid.identity'] }; var json = Y.JSON.stringify(session); // Cache the session in a cookie Y.Cookie.set(Y.login.cookieName, json); // Notify anyone who's listening in this yui sandbox that the session's ready Y.fire(Y.login.sessionReadyEventName); // Stop polling popup & close it timer.cancel(); popup.close(); } catch(e) { Y.log(e); } }, '', true); // Self-close login window if user hasn't completed flow in 30 sec Y.later(30000, Y, function () { timer.cancel(); popup.close(); }); }; // This function creates markup and event handling for a login button // @param {string} id is the id of the DOM element to insert login button into // @param {string} html is an optional param for custom button markup // @throws Error if an id is not passed in // @throws Error if there is no DOM element with the id passed in Y.login.renderLoginButton = function (id, html) { if (!id) { throw new Error('Y.login.renderLoginButton - A DOM element id is a required argument'); } var button = Y.one('#'+id); if (!button) { throw new Error('Y.login.renderLoginButton - No DOM element with id "'+id+'" found'); } html = html || '<img src="http://l.yimg.com/a/i/reg/openid/buttons/1_new.png"/>'; button.set('innerHTML', html); Y.on(Y.login.sessionReadyEventName, function () { // login handling is async, so remove handler after auth is complete Y.Event.purgeElement(button); Y.login.renderLogoutButton(id); }); // check for previously saved session var session = Y.Cookie.get(Y.login.cookieName); // if there is a session, fire session ready event and exit early if (session) { Y.fire(Y.login.sessionReadyEventName); return; } button.on('click', function(e) { Y.login.popup(); }); }; // This function creates markup and event handling for a logout button // @param {string} id is the id of the DOM element to insert login button into // @param {string} html is an optional param for custom button markup // @throws Error if an id is not passed in // @throws Error if there is no DOM element with the id passed in Y.login.renderLogoutButton = function (id, html) { if (!id) { throw new Error('Y.login.renderLogoutButton - A DOM element id is a required argument'); } var button = Y.one('#'+id); if (!button) { throw new Error('Y.login.renderLogoutButton - No DOM element with id "'+id+'" found'); } //fetch username for display to make the login/logout a bit more realistic var json = Y.Cookie.get(Y.login.cookieName); var session = Y.JSON.parse(json); html = html || 'User id: '+session['username']+'<br><a href="#">log out</a>'; button.set('innerHTML', html); var handler = button.on('click', function(e) { e.preventDefault(); // remove session cookie Y.Cookie.remove(Y.login.cookieName); // remove logout click handler Y.Event.purgeElement(button); // restore login button Y.login.renderLoginButton(id); }); }; }, '', {requires:['cookie', 'event', 'querystring', 'json', 'node']});