Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to create a login/logout form in Scala using Lift

As more and more people are interested in Scala (like myself), rather than a question, I'd like to discuss one implementation of a login/logout snippet for a webapp based on Lift.

I just started to learn Scala and Lift so it's probably not the best way to implement such a feature but I'd like to share it for other beginners and discuss it with more experienced developers. Please note that I'm also not an expert in web development. Any help for improvements would be greatly appreciated (especially performance and security related ones) ;-)

1) First of all, the snippet needs to be easily plugable, like with 1 line of code in your default template. I've done it using the embedded Lift feature (notice the underscore so it can't be rendered as a page itself but only invoked from a rendered page, in short, some kind of "private" snippet):

<lift:embed what="_logInForm" />

2) Then, in _logInForm.html, I use the below markup and a conditional display to handle everything:

<div>
    <!-- User is not logged in, show a form to log in using the method loggedOut -->
    <lift:LogInForm.loggedOut>
        <form class="lift:LogInForm.logIn?form=post">
            <label for="textName">Username: </label><input type="text" id="textName" name="name" /> <span class="lift:Msg?id=name;errorClass=error"/><br/>
            <label for="textPassword">Password: </label><input type="password" id="textPassword" name="password" /> <span class="lift:Msg?id=password;errorClass=error"/><br/>
            <input type="submit" value="Log in" />
        </form>
    </lift:LogInForm.loggedOut>

    <!-- User is logged in, show who she is and a way to log out using the method loggedIn -->
    <lift:LogInForm.loggedIn>
        <form class="lift:LogInForm.logOut?form=post">
        Connected as <span class="lift:LogInForm.getName" />.<br />
        <input type="submit" id="btnLogOut" value="Log out" />
        </form>
    </lift:LogInForm.loggedIn>
</div>

3) ... and now the Scala/Lift logic behind this markup:

object LogInForm {
  private object name extends SessionVar("")
  private object password extends RequestVar("")
  private object referer extends RequestVar(S.referer openOr "/")
  var isLoggedIn = false

  def loggedIn(html: NodeSeq) =
    if (isLoggedIn) html else NodeSeq.Empty

  def loggedOut(html: NodeSeq) =
    if (!isLoggedIn) html else NodeSeq.Empty

  def logIn = {
    def processLogIn() {
      Validator.isValidName(name) match {
        case true => {
          Validator.isValidLogin(name, password) match {
            case true => { isLoggedIn = true } // Success: logged in
            case _ => S.error("password", "Invalid username/password!")
          }
        }
        case _ => S.error("name", "Invalid username format!")
      }
    }

    val r = referer.is
    "name=name" #> SHtml.textElem(name) &
      "name=password" #> (
        SHtml.textElem(password) ++
          SHtml.hidden(() => referer.set(r))) &
      "type=submit" #> SHtml.onSubmitUnit(processLogIn)
  }

  def logOut = {
    def processLogOut() { isLoggedIn = false }
    val r = referer.is
    "type=submit" #> SHtml.onSubmitUnit(processLogOut)
  }

  def getName = "*" #> name.is
}

Comments:

  • The selection between the two forms is made by the logic, rendering either the provided markup or NodeSeq.Empty, based on the fact the user is either logged in or logged out.
  • I used Lift:Msg to have error messages next to the appropriate fields (name/password). The message is sent using S.error in the logic behind and the appropriate id.
  • I actually perform the checks in a Validator helper using regexps and formats checks, etc. This return a boolean each time to pattern matching is trivial.
  • I use the referer so the user can log in / log out while staying on the same page.
  • The username is kept as a session variable and shown when logged in.

4) You can control access to other pages doing the following in Boot.scala:

    def sitemap() = SiteMap(
      Menu("Home") / "index",
      Menu("Protected page") / "protectedPageName" >> If(() => LogInForm.isLoggedIn, ""),
      // etc.

Questions:

  1. No SSL protection (that could be an improvement, haven't had a look at this in Scala/Lift yet). Any experience from someone else could be useful?
  2. Use of session variable. Maybe there is a better way to keep state in Scala/Lift?
  3. Is there already something made especially for logging in/out in Lift that I could have missed?
  4. It's not very long, but could maybe be more compact? (I'd like not to sacrifice too much readability though. Other developers need to quickly understand it)
  5. Any other suggestion?

Cheers,

Marc.

like image 214
Marc Carré Avatar asked Oct 10 '22 23:10

Marc Carré


1 Answers

  1. This shows how to force use of SSL, you will typically use that for the login form page menu: Lift filter to force ssl
  2. A session variable is typically best way to keep session information
  3. ProtoUser (you can use Lifty (http://lifty.github.com/) to setup a project with login as an example)
like image 102
thoredge Avatar answered Oct 14 '22 04:10

thoredge