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
}
}
play-auth: 0.13.0 we use HMAC as follows
https://github.com/megamsys/megam_gateway/blob/0.7/app/controllers/stack/APIAuthElement.scala
https://github.com/megamsys/megam_gateway/blob/0.7/app/controllers/stack/SecurityActions.scala
Controllers that implement trait APIAuthElement, and have StackAction will get HMAC verified
https://github.com/megamsys/megam_gateway/blob/0.7/app/controllers/Accounts.scala