t2v/play2-auth

Alternative for mechanism for authentication

Otann opened this issue · 7 comments

Hello!

You have a wonderful lib and I strongly prefer this one over other more complex alternatives for web projects.

However I would like to use same lib for building API for mobile application, which will require to replace cookie mechanism with something like headers (like X-AUTH-TOKEN)

First question
Is there any option to replace cookies with custom request => token and token => request conversions?

Looking briefly at code I think the right way would be to extend CookieSupport and mix it in to implementation of AuthConfig.

Second Question
Would you like to abstract CookieSupport? I can make pull request but would like to discuss the way to do it.

Hi, thanks for sending proposal.

I'm very interesting that adding alternative mechanism.

First question

At current version(0.13.0), there is no option.
However, I agree that creating the custom way.
As concern, request => token looks good, bad token => request conversion is impossible.
so It needs token => Result => Result or so on.

Second Question

Yes. It seems good idea that we make CookieSupport abstract.
Now I have no idea of new trait name.
If you send a PR, I welcome it :)

Sorry, I've meant token => (Result => Result) of course :)

Ok, I will try to play with code and submir PR next wek.
Thank you for your support!

Hi, @Otann

I added TokenAccessor into play2-auth 0.13.1-SNAPSHOT

And create a sample of HTTP Basic Authentication https://github.com/t2v/play2-auth/tree/946af6164a8dd57b1b1d468cba3f77c259875138/sample/app/controllers/basic

What do you think about it?

Oh, thats looks great!
I've been thinking about pretty same implementation.

But for the lack of time I've implemented overriden traits for now.
I will try to check your code on weekend and provide implementation for headers.

For now I use this:

CustomCacheIdContainer.scala

package jp.t2v.lab.play2.auth

import scala.reflect.ClassTag

/** Enables authentication for separate entities */
class CustomCacheIdContainer[Id: ClassTag](suffix: String) extends CacheIdContainer[Id] {

  override private[auth] val tokenSuffix = s":$suffix:token"
  override private[auth] val userIdSuffix = s":$suffix:userId"

}

HeaderCookieSupport.scala

package jp.t2v.lab.play2.auth

import play.api.libs.Crypto
import play.api.mvc.{DiscardingCookie, RequestHeader, Controller, Result}

import scala.concurrent.{Future, ExecutionContext}

/**
 * Rewrites authentication mechanism of storing/retrieval token in Request/Result
 * @see [[jp.t2v.lab.play2.auth.CookieUpdater]]
 * @see [[jp.t2v.lab.play2.auth.AuthenticityToken]]
 *
 * Mapping of concepts
 * CookieName -> HeaderName
 */

/**
 * Mixin for async authorization
 * Your secured controllers should mix in this instead of AsyncAuth
 */
trait HeaderAsyncAuth extends AsyncAuth with HeaderSupport { self: AuthConfig with Controller =>

  private[auth] override def restoreUser(implicit request: RequestHeader, context: ExecutionContext): Future[(Option[User], CookieUpdater)] = {
    (for {
      header <- request.headers.get(cookieName)
      token  <- verifyHeaderHmac(header)
    } yield for {
        Some(userId) <- idContainer.get(token)
        Some(user)   <- resolveUser(userId)
        _            <- idContainer.prolongTimeout(token, sessionTimeoutInSeconds)
      } yield {
        Option(user) -> bakeCookie(token) _
      }) getOrElse {
      Future.successful(Option.empty -> identity)
    }
  }



}

/**
 * Mixin for normal authorization
 * Your Authorization controller should mix in this instead of LoginLogout
 */
trait HeaderLoginLogout extends LoginLogout with HeaderSupport { self: AuthConfig with Controller =>

  override def gotoLogoutSucceeded(result: => Future[Result])(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = {
    request.headers.get(cookieName) flatMap verifyHeaderHmac foreach idContainer.remove
    result
  }

}

/**
 * Override cookie baking mechanics and instead responds with header
 * your AuthConfig should mix in this
 */
trait HeaderSupport extends CookieSupport { self: AuthConfig =>

  override def bakeCookie(token: String)(result: Result): Result = {
    val value = Crypto.sign(token) + token
    result.withHeaders(cookieName -> value)
  }

  def verifyHeaderHmac(headerValue: String): Option[String] = {
    val (signature, token) = headerValue.splitAt(40)
    if (Crypto.sign(token) == signature) Some(token) else None
  }

}

@indykish
That's nice! thanks for cool using :)

@Otann
thanks for trying.
Now, I has published 0.13.1 so I close this issue.
If there are problems, please reopen or create a new issue.