Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

A Simple OpenID Selector

Monday, 6 December, 2010 @ 5:44 PM < Adam Boddington
Tags: Building Neno, jQuery, OpenID

This is post #13 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

I need an OpenID selector control on my login screen to make it easier for users to enter their OpenID identifiers. The most popular one around seems to be openid-selector or some variation of it. While a comprehensive control, I had a play with the demo and found a few things I didn't love out of the box.

  1. The sprite image regeneration. I have to have ImageMagick installed to add new OpenID providers.
  2. The button animation. I don't have animation anywhere on my site (yet), and this animation feels a little clunky to me.
  3. The cookie. Potentially a nice convenience, but I don't fully understand what it is doing, so at this stage I don't want it. I only want to set one cookie on my site for now, and that's the forms authentication cookie.

Two and three can probably be fixed with configuration, but there are no easy examples on how to do that on the openid-selector site. I could get openid-selector to play nice, or I could roll my own minimalist version with jQuery. One guess which way I went.

The HTML

I want to keep my HTML light and easy to understand.

<h2>Login</h2>
<div id="openid" class="append-bottom">
    <p>
        Please click your OpenID provider or enter your OpenID identifier below.
    </p>
    <div>
        <a id="google"></a>
        <a id="yahoo"></a>
        <a id="aol"></a>
        <a id="myopenid"></a>
    </div>
    <div class="clear">
        <a id="livejournal"></a>
        <a id="wordpress"></a>
        <a id="blogger"></a>
        <a id="verisign"></a>
        <a id="claimid"></a>
        <a id="clickpass"></a>
        <a id="google_profile"></a>
    </div>
    <div class="clear">
    </div>
</div>
<%
    using (Html.BeginForm("authenticate", "user", new { ReturnUrl = Request["ReturnUrl"] }, FormMethod.Post))
    {%>
    <p id="openid_username_block">
        <label for="openid_username"></label><br />
        <input id="openid_username" name="openid_username" type="text" />
    </p>
    <p>
        <label for="openid_identifier">OpenID</label><br />
        <input id="openid_identifier" name="openid_identifier" type="text" />
    </p>
    <p>
        <input type="submit" value="Login" />
    </p>
<%
    }%>

The idea is to hide the #openid block until everything is formatted, loaded and ready to use. Each anchor will be an OpenID provider button. The anchors in the first block will be large buttons, and the anchors in the second block will be small buttons. I want to be able to easily add providers to either the top or bottom.

The #openid_username input is for the OpenID providers whose identifiers are based on a username. The #openid_username_block block will be hidden initially too. I included it in the form so that the user can just hit enter when they're done.

The CSS

There's a little CSS to hide the two blocks mentioned above. The rest of the CSS, heavily influenced by openid-selector, formats the buttons.

a.openid-large
{
    background-position: center center;
    background-repeat: no-repeat;
    border: 1px solid #ddd;
    float: left;
    height: 60px;
    margin: 0 5px 5px 0;
    width: 100px;
}

a.openid-small
{
    background-position: center center;
    background-repeat: no-repeat;
    border: 1px solid #ddd;
    float: left;
    height: 24px;
    margin-right: 5px;
    width: 24px;
}

div#openid
{
    display: none;
}

p#openid_username_block
{
    display: none;
}

The JavaScript

An array is a handy way to define the important bits about each provider.

var providers = {
    google : {
        name : 'Google',
        username : false,
        url: 'https://www.google.com/accounts/o8/id'
    },
    yahoo : {
        name : 'Yahoo',
        username : false,
        url: 'http://me.yahoo.com/'
    },
    aol : {
        name : 'AOL',
        username : true,
        url: 'http://openid.aol.com/{username}'
    },
    // ...
}

The first thing to do is format everything, get the click events defined, and then display the #openid block.

$(document).ready(function () {
    $('div#openid a').attr('href', function () { return 'javascript:openid(\'' + this.id + '\');' });
    $('div#openid a').attr('title', function () { return providers[this.id].name });
    $('div#openid div').first().children('a').addClass('openid-large').parent().next().children('a').addClass('openid-small');
    $('div#openid a').css('background-image', function () { return 'url("<%=Url.Content("~/content/openid/")%>' + this.id + '.gif")' });
    $('input#openid_identifier').focus();
    $('div#openid').css('display', 'inherit');
});

The first line sets the href of the anchors to a JavaScript method I'll display shortly. The second line sets the title of each anchor to the name of the provider. The third line adds classes to the anchors -- openid-large for the anchors in the first div and openid-small for the anchors in the second div. The fourth line sets the background image of each anchor. I use an MVC convenience method, Url.Content to get the correct path for the images. The fifth and sixth lines set the focus and reveal the result!

Login

So what about the magic? What happens when a user clicks on a button?

function openid(id) {
    var provider = providers[id];
    var username_block = $('p#openid_username_block');
    var username = $('input#openid_username');
    var input = $('input#openid_identifier');
    var form = input.parents('form');
    if (provider.username) {
        username_block.css('display', 'inherit');
        username_block.children('label').html(provider.name + ' Username');
        username.keypress(function () { setTimeout(function () { input.val(provider.url.replace('{username}', username.val())) }, 500) });
        username.focus();
        input.val(provider.url.replace('{username}', username.val()));
    }
    else {
        username_block.css('display', 'none');
        input.val(providers[id].url);
        input.focus();
        form.submit();
    }
};

Just what you would expect. If the user clicks on a provider that includes a username in the identifier, the #openid_username_block block is shown, the label is set, and the keypress event of the #openid_username input is set to update the #openid_identifier input automagically. That same update operation is also called once in case there is already a value in the #openid_username input -- handy if the user clicks the wrong button but doesn't realise it until after entering their username.

Login with Username

If the user hits Google or Yahoo!, the two providers that don't require a username, the form is submitted automatically.

Conclusion

In the end it wasn't that hard to roll my own simple OpenID selector. The basics are there, including a graceful degradation if JavaScript is disabled, and a decent amount of flexibility for maintaining existing providers or adding new providers when they arise. Test it out for yourself. Login with an OpenID -- there's no need to actually register with my site if you don't want to.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Implementing OpenID in ASP.NET MVC

Newer
Speed Bumps

Older
Implementing OpenID in ASP.NET MVC

Newer
Speed Bumps

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM