Sunday, December 25, 2011

Lift & OpenID. Customized login / logout form

Lately I've playing with Scala and Lift framework.
While building a web application, sooner or later you'll have to face the issue of user management. Fortunatley, Lift comes with OpenID support, to your work is  very little. I don't want to annoy you with an how-to that is already available in official Lift documentation at this URL.
What I find missing was a very user-friendly login form: the one provided expect the user to know his OpenID URL, which is very unlikely. A user may know his Google / AOL / Yahoo! credentials, but not the URL associated with them. Honestly, I'm one of them! :-)
I found a very user friendly login form built with jQuery here.
So I decided to build a Lift snippet once for all wrapping a full management of login / logout operations.
The purposes were:

  • have a user friendly login form
  • support for
    - Google
    - AOL
    - Yahoo!
    - OpenID
  • A widget embeddable in any web page to show the current user and a logout button or a link to the login page
The result is a Scala class:
import scala.xml.{ Text ⇒ T }
import scala.xml.NodeSeq
import net.liftweb.http.S

abstract class Login {
  
  /**
   * Builds the login form.
   * Parameters:
   * <ul>
   * <li><b>return_url</b>: the url the user is redirected to after successful login</li>
   * <li><b>login_enterusername</b>:</li>
   * <li><b>login_enteropenid</b>:</li>
   * <li><b>login_login</b>:</li>
   * </ul>
   */
  def login = {
    val r = S.param("return_url") openOr "/index"

    <head>
      <link rel="stylesheet" type="text/css" media="screen" href={S.param("login_css") openOr ""}/>
      <script type="text/javascript" src={S.param("jqueryopenid_js") openOr ""}></script>
      <script type="text/javascript">  { "$(function() { $(\"form.openid:eq(0)\").openid(); });" }</script>
    </head>
    <h2>{ S.?(S.param("login_title") openOr "Login") }</h2>
    <form class="openid" method="post" action={ (S.param("login_url") openOr "/openid/login") + "?ReturnUrl=" + {S.param("return_url") openOr "/index"} }>
      <div>
        <ul class="providers">
          <li class="openid" title="OpenID"><img src="/classpath/org/scz/openid/ui/images/openidW.png" alt="icon"/> <span>
                                                                                                                      <strong>{ "http://{your-openid-url}" }</strong>
                                                                                                                    </span></li>
          <li class="direct" title="Google">
            <img src="/classpath/org/scz/openid/ui/images/googleW.png" alt="icon"/>
            <span>https://www.google.com/accounts/o8/id</span>
          </li>
          <li class="direct" title="Yahoo"><img src="/classpath/org/scz/openid/ui/images/yahooW.png" alt="icon"/><span>http://yahoo.com/</span></li>
          <li class="username" title="AOL screen name"><img src="/classpath/org/scz/openid/ui/images/aolW.png" alt="icon"/><span>
                                                                                                                             http://openid.aol.com/<strong>username</strong>
                                                                                                                           </span></li>
          <li class="username" title="MyOpenID user name">
            <img src="/classpath/org/scz/openid/ui/images/myopenid.png" alt="icon"/><span>http://<strong>username</strong>.myopenid.com/</span>
          </li>
          <li class="username" title="Flickr user name">
            <img src="/classpath/org/scz/openid/ui/images/flickr.png" alt="icon"/><span>http://flickr.com/<strong>username</strong>/</span>
          </li>
          <li class="username" title="Technorati user name">
            <img src="/classpath/org/scz/openid/ui/images/technorati.png" alt="icon"/><span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
          </li>
          <li class="username" title="Wordpress blog name">
            <img src="/classpath/org/scz/openid/ui/images/wordpress.png" alt="icon"/><span>http://<strong>username</strong>.wordpress.com</span>
          </li>
          <li class="username" title="Blogger blog name">
            <img src="/classpath/org/scz/openid/ui/images/blogger.png" alt="icon"/><span>http://<strong>username</strong>.blogspot.com/</span>
          </li>
          <li class="username" title="LiveJournal blog name">
            <img src="/classpath/org/scz/openid/ui/images/livejournal.png" alt="icon"/><span>http://<strong>username</strong>.livejournal.com</span>
          </li>
          <li class="username" title="ClaimID user name"><img src="/classpath/org/scz/openid/ui/images/claimid.png" alt="icon"/><span>
                                                                                                                                  http://claimid.com/<strong>username</strong>
                                                                                                                                </span></li>
          <li class="username" title="Vidoop user name">
            <img src="/classpath/org/scz/openid/ui/images/vidoop.png" alt="icon"/><span>http://<strong>username</strong>.myvidoop.com/</span>
          </li>
          <li class="username" title="Verisign user name">
            <img src="/classpath/org/scz/openid/ui/images/verisign.png" alt="icon"/><span>http://<strong>username</strong>.pip.verisignlabs.com/</span>
          </li>
        </ul>
      </div>
      <fieldset>
        <label for="openid_username">{ S.?(S.param("login_enterusername") openOr "Enter your username") }</label>
        <div>
          <span></span><input type="text" name="openid_username"/><span></span>
          <input type="submit" value="Login"/>
        </div>
      </fieldset>
      <fieldset>
        <label for="openid_identifier">{ S.?(S.param("login_enteropenid") openOr "Enter your OpenID") }</label>
        <div>
          <input type="text" name="openid_identifier"/><input type="submit" value={S.?(S.param("login_login") openOr "Login")}/>
        </div>
      </fieldset>
    </form>
  }

  /**
   * Widget shown where user is authenticated.<br/>
   * If user is logged a logout form will be shown.<br/>
   * If the user is not logged in, a link to the login page will be shown. Parameters
   * <ul>
   * <li><b>login_widget_as</b></li>
   * <li><b>login_widget_logout</b></li>
   * <li><b>login_logout_path</b></li>
   * <li><b>login_path</b></li>
   * <li><b>login_widget_login</b></li>
   * </ul>
   */
  def widget: NodeSeq = {
    if (authenticated())
      <span id="logout">
        { S.?(S.param("login_widget_as") openOr "Logged in as") }
         <span>{ currentUser() }</span>
         
        <a id="logout_button" href="#">{ S.?(S.param("login_widget_logout") openOr "Logout") }</a>
        <script>{ "$('#logout_button').click(function(){document.forms['openid_form_logout'].submit();})" }</script>
        <form style="display: none" id="openid_form_logout" method="post" action={ S.param("login_logout_path") openOr "/openid/logout" }></form>
      </span>
    else
      <span id="login"><a href={ S.param("login_path") openOr "/login" }>{
        S.?(S.param("login_widget_login") openOr "Login")
      }</a></span>
  }
  
  protected val authenticated: () => Boolean
  protected val currentUser: () => String
}

As you can see, the class is abstract and provides two vals that must be overridden:
val authenticated: ()=>Boolean
val currentUser: ()=>String
The other two functions provided builds respectively the form and the login / logout widget.
Calling this snippets you can add the parameters as specified in the scaladoc comments to customize the labels, that can be localized too.

Hope you'll find usefull

No comments:

Post a Comment