These are chat archives for mohiva/play-silhouette

30th
May 2015
David Hoyt
@davidhoyt
May 30 2015 09:21 UTC
hi all -- attempting to use the JWTAuthenticatorService. It's now authenticating and issuing a JWT. I then would like to redirect to an alternative page for paying customers. :) However I'm noticing the browser doesn't send the newly minted JWT in the request headers (makes sense that it doesn't). Suggestions on what to do?
Thought was to also set a cookie w/ the same token value and look for the header first and then the cookie if the header isn't found. Is there already something like this available? Would really like to stick w/ using JWTs if possible.
David Hoyt
@davidhoyt
May 30 2015 09:28 UTC
(to clarify -- the issue is that I authenticate, then issue a redirect to an action that's using SecuredAction and because the JWT isn't sent in the request by the browser after the redirect, the action fails and the onNotAuthenticated error handler is called).
David Hoyt
@davidhoyt
May 30 2015 09:34 UTC
Also small typo in JWTAuthenticator.scala line 410: teplaces
David Hoyt
@davidhoyt
May 30 2015 18:48 UTC
I'm still hoping for a better solution, but for those interested, my JWT + cookie solution appears to be working:
case class JWTAuthenticatorWithCookieSettings(
  cookieName: String = "auth-token",
  cookiePath: String = "/",
  cookieDomain: Option[String] = None,
  secureCookie: Boolean = true, // Default to sending only for HTTPS in production, but not for development and test.
  httpOnlyCookie: Boolean = true,
  maxAge: Option[Int] = Some(12 * 60 * 60)
)

class JWTAuthenticatorServiceWithCookie(cookieSettings: JWTAuthenticatorWithCookieSettings, settings: JWTAuthenticatorSettings, dao: Option[AuthenticatorDAO[JWTAuthenticator]], idGenerator: IDGenerator, clock: Clock)(implicit ec: ExecutionContext)
  extends JWTAuthenticatorService(settings, dao, idGenerator, clock)(ec) {
  //import play.api.libs.concurrent.Execution.Implicits.defaultContext

  private[this] def cookieFor(value: String): Cookie =
    Cookie(
      value    = value,
      name     = cookieSettings.cookieName,
      maxAge   = cookieSettings.maxAge,
      path     = cookieSettings.cookiePath,
      domain   = cookieSettings.cookieDomain,
      secure   = cookieSettings.secureCookie,
      httpOnly = cookieSettings.httpOnlyCookie
    )

  private[this] def discardCookie: DiscardingCookie =
    DiscardingCookie(
      name   = cookieSettings.cookieName,
      path   = cookieSettings.cookiePath,
      domain = cookieSettings.cookieDomain,
      secure = cookieSettings.secureCookie
    )

  override def retrieve(implicit request: RequestHeader): Future[Option[JWTAuthenticator]] = {
    import com.mohiva.play.silhouette._

    def doUnserialize(token: String) =
      unserialize(token)(settings) match {
        case Success(authenticator) =>
          dao.fold(Future.successful(Option(authenticator)))(_.find(authenticator.id))

        case Failure(e) =>
          logger.info(e.getMessage, e)
          Future.successful(None)
      }

    Future.from(Try(request.headers.get(settings.headerName))).flatMap {
      //Found the auth header.
      case Some(token) =>
        doUnserialize(token)

      //Unable to find the header, see if there's a cookie that we can use.
      case None =>
        request.cookies.get(cookieSettings.cookieName) match {
          case Some(cookie) =>
            doUnserialize(cookie.value)

          case None =>
            Future.successful(None)
        }
    } recover {
      case e =>
        throw new AuthenticatorRetrievalException(RetrieveError.format(ID), e)
    }
  }

  override def embed(token: String, result: Result)(implicit request: RequestHeader): Future[AuthenticatorResult] =
    Future.successful(AuthenticatorResult(
      result
        .withHeaders(
          settings.headerName -> token,
          HeaderNames.PRAGMA -> "no-cache",
          HeaderNames.CACHE_CONTROL -> "no-cache"
        )
        .withCookies(cookieFor(token))
    ))

  override def embed(token: String, request: RequestHeader): RequestHeader = {
    val cookies = Cookies.mergeCookieHeader(request.headers.get(HeaderNames.COOKIE).getOrElse(""), Seq(cookieFor(token)))
    request.copy(headers = request.headers.replace(
      settings.headerName -> token,
      HeaderNames.COOKIE -> cookies
    ))
  }

  override def discard(authenticator: JWTAuthenticator, result: Result)(implicit request: RequestHeader): Future[AuthenticatorResult] = {
    dao.fold(Future.successful(()))(_.remove(authenticator.id)).map { _ =>
      AuthenticatorResult(result.discardingCookies(discardCookie))
    } recover {
      case e =>
        throw new AuthenticatorDiscardingException(DiscardError.format(ID, authenticator), e)
    }
  }
}
David Hoyt
@davidhoyt
May 30 2015 19:59 UTC