Samstag, 8. Juni 2013

Social login with RPXNow in Lift

I just got Social login (OpenID) working in Lift, using RPXNow. RPXNow is intented to make OpenID login easy. It's free bellow 6 providers.

This guide assumes you have already setup the application in rpxnow.

Here are the steps:

1. Add this JS to the page where login will appear (preferably in head):

(function() {
   if (typeof window.janrain !== 'object') window.janrain = {};
   if (typeof window.janrain.settings !== 'object') window.janrain.settings = {};
   janrain.settings.tokenUrl = 'http://localhost:8080/signupr';

    function isReady() { janrain.ready = true; };
   if (document.addEventListener) {
     document.addEventListener("DOMContentLoaded", isReady, false);
   } else {
     window.attachEvent('onload', isReady);

    var e = document.createElement('script');
   e.type = 'text/javascript'; = 'janrainAuthWidget';

    if (document.location.protocol === 'https:') {
     e.src = '';
   } else {
     e.src = '';

    var s = document.getElementsByTagName('script')[0];
   s.parentNode.insertBefore(e, s);
- http://localhost:8080/signupr is obviously my testing host + signupr is how I named the path to my webservice handler - see step 3.
- -> "myapp" is the name of the app given in RPXNow.

  2. Add a placeholder HTML element with id "janrainEngageEmbed" where the login widget has to be inserted: In my case, I want to use it together with the existing login system of lift - so I added it just above lift's login form. In order to do that, I overwrote screenWrap in User singleton:
 override def screenWrap = Full{
3. Add handler to get the token from JS call and sign in the user:
class Boot extends Loggable {
 def boot {
   LiftRules.dispatch.append {
     case req @ Req(List("signupr"), _, _) =>
     val userData:SigninResponse = getLoggedInUserData(S.param("token").openOr(""))
    () => for (hrs <- Box.asA[net.liftweb.http.provider.servlet.HTTPRequestServlet](req.request)) yield {
     new RedirectResponse("/page_to_load_after_login.html", null)
getLoggedInUserData calls rpxnow api to get logged in user's data:
    def getLoggedInUserData(token:String):SigninResponse = {
      val query = Map(
        "apiKey" -> "myapikey",
        "token" -> token

     var queryStr = query.foldLeft("") { (s: String, pair: (String, String)) =>
      s + pair._1 + "=" + pair._2 + "&"
     } //TODO without "&" at the end
     queryStr = queryStr.substring(0, queryStr.length - 1)
     val url = new URL("")
     val conn:HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection] 
     val osw = new OutputStreamWriter(conn.getOutputStream(), "UTF-8")
     val response = streamToString(conn.getInputStream())
     implicit val formats = net.liftweb.json.DefaultFormats //need this for extract to work
     val userData:SigninResponse = parse(response).extract[SigninResponse]
SigninResponse class is just a model class for the returned JSON. The field names may change depending of the provider (there are also more fields). I use with these fields, which I need and are also common in the providers I use:
 case class SigninResponse(stat: String, profile:SigninProfile)
 case class SigninProfile(providerName: String, identifier:String, name:SigninName, displayName:String)
 case class SigninName(formatted: String, givenName:String, familyName:String)
At last this method to add the user to my user database (just to keep track of them), and set the user in the session:
 def loginOrRegisterUser(userData:SigninResponse) = {
  var userBox:Box[User] = User.find(By(User.externId, userData.profile.identifier))
  if (userBox.isEmpty) {
   userBox = Full(User.create)

  } else {
//   user = userBox.get
  val user:User = userBox.get

    User.logUserIn(user) //set user in the session
In order for that to work I extended the user object / table with provider, externId and username. For the users registered with the default system, externid and provider are never set.

4. Now I can access these users in the app, like the default users:

How to load translations in Lift from a database

I added this in Boot.scala:

LiftRules.resourceBundleFactories.append {
    case (key, locale) => new TranslationsResourceLoader(locale)

And this is the TranslationsResourceLoader class:

import java.util.Locale
import java.util.ResourceBundle

//important! otherwise scala's Enumeration is used and we get
//compiler errors
import java.util.Enumeration 

class TranslationsResourceLoader(val locale:Locale)
  extends ResourceBundle {

  private val translations:List[Translation] = Translation.findAll()
    .filter(t => t.lang.equals(locale.toString.split("_")(0)))

  def getKeys(): Enumeration[String] = {
   val it = translations.iterator
   new Enumeration[String] {
     def hasMoreElements() = it.hasNext
     def nextElement() =

  def handleGetObject(key: String):String = {
   translations.find(t => t.tkey.equals(key)).get.value
And that's it! Now you can use the translations with the usual internationalization methods. In HTML this would be:

<lift:loc locid="dummykey">translation test<lift:loc>


<p class="lift:loc?locid="dummykey">translation test<p>

And in code:

import net.liftweb.http.S.?


Note: This assumes a (custom) class "Translation". In my case this is mapped to a Translation database table. I'm using Lift's Mapper.

Here are useful links about internationalization in Lift in general: