vendredi 17 juin 2016

Session works on localhost but not on server

I am using play2-auth for authentication and authorization in my app. It works just fine on localhost, but when I upload my application to production server - it fails.

The problem is that loginSucceeded gets called but when user is redirected to required page, authentication fails and user is redirected back to login. Cookies and session storage stays empty.

Maybe someone have any ideas where is the problem?

EDIT: Adding some code

I have BaseAuthConfig trait:

trait BaseAuthConfig  extends AuthConfig {

  /**
    * A type that is used to identify a user.
    * `String`, `Int`, `Long` and so on.
    */
  type Id = Int

  /**
    * A type that represents a user in your application.
    * `User`, `Account` and so on.
    */
  type User = Account

  /**
    * A type that is defined by every action for authorization.
    *
    * sealed trait Role
    * case object Administrator extends Role
    * case object Master extends Role
    */
  type Authority = Role

  /**
    * A `ClassTag` is used to retrieve an id from the Cache API.
    * Use something like this:
    */
  val idTag: ClassTag[Id] = classTag[Id]

  /**
    * The session timeout in seconds
    */
  val sessionTimeoutInSeconds = 3600

  /**
    * A function that returns a `User` object from an `Id`.
    */
  def resolveUser(id: Id)(implicit ctx: ExecutionContext) = Future.successful(Account.findById(id))


  def authorizationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = throw new AssertionError("don't use")

  /**
    * If authorization failed (usually incorrect password) redirect the user as follows:
    */
    override def authorizationFailed(request: RequestHeader, user: User, authority: Option[Authority])(implicit ctx: ExecutionContext) = {
    Future.successful(Forbidden("no permission"))
  }

  /**
    * A function that determines what `Authority` a user has.
    */
  def authorize(user: User, authority: Authority)(implicit ctx: ExecutionContext) = {
    Future.successful((user.role, authority) match {
      case (Administrator, Administrator) => true
      case (Master, _) => true
      case _ => false
    })
  }

  override lazy val idContainer: AsyncIdContainer[Id] = new AsyncIdContainer[Id] {

    private val tokenSuffix = ":token"
    private val userIdSuffix = ":userId"
    private val random = new Random(new SecureRandom())

    override def startNewSession(userId: Id, timeoutInSeconds: Int)(implicit request: RequestHeader, context: ExecutionContext): Future[AuthenticityToken] = {
      removeByUserId(userId)
      val token = generate()
      store(token, userId, timeoutInSeconds)

      Future.successful(token)
    }

    @tailrec
    private final def generate(): AuthenticityToken = {
      val table = "abcdefghijklmnopqrstuvwxyz1234567890_.~*'()"
      val token = Iterator.continually(random.nextInt(table.size)).map(table).take(64).mkString
      if (syncGet(token).isDefined) generate() else token
    }

    private def removeByUserId(userId: Id) {
      GlobalMap.container.get(userId.toString + userIdSuffix).map(_.asInstanceOf[String]) foreach unsetToken
      unsetUserId(userId)
    }

    override def remove(token: AuthenticityToken)(implicit context: ExecutionContext): Future[Unit] = {
      get(token).map(_ foreach unsetUserId)
      Future.successful(unsetToken(token))
    }

    private def unsetToken(token: AuthenticityToken) {
      GlobalMap.container.remove(token + tokenSuffix)
    }
    private def unsetUserId(userId: Id) {
      GlobalMap.container.remove(userId.toString + userIdSuffix)
    }

    override def get(token: AuthenticityToken)(implicit context: ExecutionContext): Future[Option[Id]] = {
      Future.successful(syncGet(token))
    }

    private def syncGet(token: AuthenticityToken): Option[Id] = {
      GlobalMap.container.get(token + tokenSuffix).map(_.asInstanceOf[Id])
    }

    private def store(token: AuthenticityToken, userId: Id, timeoutInSeconds: Int) {
      GlobalMap.container.put(token + tokenSuffix, userId.asInstanceOf[AnyRef]/*, timeoutInSeconds*/) // TODO:
      GlobalMap.container.put(userId.toString + userIdSuffix, token.asInstanceOf[AnyRef]/*, timeoutInSeconds*/) // TODO:
    }

    override def prolongTimeout(token: AuthenticityToken, timeoutInSeconds: Int)(implicit request: RequestHeader, context: ExecutionContext): Future[Unit] = {
      Future.successful(syncGet(token).foreach(store(token, _, timeoutInSeconds)))
    }

  }

}

object GlobalMap {
  private[controllers] val container: TrieMap[String, AnyRef] = new TrieMap[String, AnyRef]()
}

Then I have a trait which extends BaseAuthConfig:

trait AuthConfigImpl extends BaseAuthConfig {

  /**
    * Where to redirect the user after a successful login.
    */
  def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Mastercms.main))

  /**
    * Where to redirect the user after logging out
    */
  def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Mastercms.login))

  /**
    * If the user is not logged in and tries to access a protected resource then redirect them as follows:
    */
  def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Mastercms.login))

}

Now in my controller:

class Mastercms extends Controller with Pjax with AuthElement with AuthConfigImpl {

  val loginForm = Form {
    mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, "")))
      .verifying("Invalid email or password", result => result.isDefined)
  }

  def main = StackAction(AuthorityKey -> Administrator) { implicit request =>
    Ok(html.master.main(loggedIn))
  }

  /**
    * Open login
   */
  def login = Action { implicit request =>
    Ok(html.master.login(loginForm))
  }

  def logout = Action.async { implicit request =>
    gotoLogoutSucceeded.map(_.flashing(
      "success" -> "You've been logged out"
    ).removingFromSession("rememberme"))
  }

  /**
    * Authentication
    * if not succeeded, return to login with errors
    *
    */
  def authenticate = Action.async { implicit request =>
    loginForm.bindFromRequest.fold(
      formWithErrors => Future.successful(BadRequest(html.master.login(formWithErrors))),
      user           => gotoLoginSucceeded(user.get.id)
    )
  }

}

Aucun commentaire:

Enregistrer un commentaire