Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

After invalidateSession I'm able to access secured endpoint with "invalidated" session #35

Open
Fruzenshtein opened this issue Apr 17, 2017 · 7 comments

Comments

@Fruzenshtein
Copy link

Hello

Looks like the invalidateSession function doesn't invalidate the session as it should. I'll explain below what I mean. If you want to look at steps to reproduce without technical details, scroll to the end :)

I use "com.softwaremill.akka-http-session" %% "core" % "0.4.0" with Scala version 2.12.1

I use following configs for session:

session {
  server-secret = "YzszrU1UkqsMqCNEnuLI8DDWs6Wqacj2z4dbtquSjB8GbsFpBA7GG38yk0DaIyrB"
  encrypt-data = true
  header {
    send-to-client-name = "Set-Authorization"
    get-from-client-name = "Authorization"
  }
}

Here is my session serialization (de-)

case class Session(role: String, email: String)
object Session {
implicit def serializer: SessionSerializer[Session, String] =
  new MultiValueSessionSerializer[Session](
    (session => Map(
      "role" -> session.role,
      "email" -> session.email)),
    (map => Try {
      Session(
        map.get("role").get,
        map.get("email").get)
    })
  )
}

And finally routes:

val routes = path("login") {
post {
  entity(as[Credentials]) { credentials =>
    onSuccess(userActor ? Authenticate(credentials)) {
      case loggedIn: LoggedIn => {
        setSession(oneOff, usingHeaders, Session(loggedIn.user.role, loggedIn.user.email)) {
          complete(HttpResponse(StatusCodes.OK))
        }
      }
      case noSuchEmail: NoUserWithEmail => complete(HttpResponse(StatusCodes.BadRequest))
      case InvalidPassword => complete(HttpResponse(StatusCodes.BadRequest))
    }
  }
}
} ~ path("me") {
get {
  requiredSession(oneOff, usingHeaders) { session =>
    complete(session.role)
  }
}
} ~ path("logout") {
post {
  requiredSession(oneOff, usingHeaders) { session =>
    invalidateSession(oneOff, usingHeaders) {
      complete(HttpResponse(StatusCodes.OK))
    }
  }
}
}

Here is what I do:

  1. Call POST /login and receive back in the header long_encrypted_token_A
  2. Call GET /me with the long_encrypted_token_A header and receive back appropriate response with ADMIN value
  3. Call POST /logout and receive back 200 response (here I assume that the session is invalidated)
  4. Call GET /me with the long_encrypted_token_A header and receive back appropriate response with ADMIN value

So the question:

Why I can still successfully can use the token after invalidation?

Thanks

@adamw
Copy link
Member

adamw commented Apr 18, 2017

If you are using the header transport, then invalidating the session responds with an empty Set-Authorization header, which is assumed to clear the client's storage. However that's of course up to your code to do that - probably it's worth documenting a bit better? (with cookies, this is done automatically by the browser).

Note that except for refreshable sessions, akka-http-session is stateless - it doesn't store the sessions anywhere, so the library itself has no way of knowing if the token was previously invalidated or not.

@Fruzenshtein
Copy link
Author

@adamw thanks for the explanation.

So this happens due that fact that in akka-http-session a session is a stateless and once it is generated I can use it until its identifier is deleted locally on a client side (browser, mobile device)

Correct?

@adamw
Copy link
Member

adamw commented Apr 18, 2017

Yes. That's why sessions should always have an expiry date :) Optionally refreshed with the refresh token - which assumes external storage and "global" invalidation.

@adamw
Copy link
Member

adamw commented Apr 18, 2017

I'll keep this open to clarify the docs later :)

@adamw adamw reopened this Apr 18, 2017
@Fruzenshtein
Copy link
Author

@adamw deal :)

@kormoglaz
Copy link

kormoglaz commented Nov 25, 2018

If you are using the header transport, then invalidating the session responds with an empty Set-Authorization header, which is assumed to clear the client's storage.

Hi all, got same problem regarding invalidation. I use InMemoryRefreshTokenStorage[T] and it stores session data well during app lifecycle. I use header transport as well and invalidating the session response with an empty Set-Authorization header, that's ok. But, regarding the sources

private[session] def invalidateRefreshableSession[T](sc: Refreshable[T], st: GetSessionTransport): Directive0 = {
    import sc.ec
    read(sc, st).flatMap {
      case None => pass
      case Some((v, setSt)) =>
        val deleteTokenOnClient = setSt match {
          case CookieST => deleteCookie(sc.refreshTokenManager.createCookie("").copy(maxAge = None))
          case HeaderST => respondWithHeader(sc.refreshTokenManager.createHeader(""))
        }

        deleteTokenOnClient &
          onSuccess(sc.refreshTokenManager.removeToken(v))
    }
  }

it should remove session from storage, especially with onSuccess(sc.refreshTokenManager.removeToken(v))
but it doesn't.

Could you clarify if everything should work as @adamw described, why this method is here and what for?

Please assume my code is similar to @Fruzenshtein implementation, expanded only by InMemoryRefreshTokenStorage[T], there is no something special.

Thank you for helping.

PS tested with scalatest and curl

@adamw
Copy link
Member

adamw commented Nov 26, 2018

@kormoglaz so you are saying that the token is not removed from storage? That should happen ... maybe you can try with a copy of InMemoryRefreshTokenStorage and with some debugging statements added.

Btw. this storage isn't mean for production, only for testing. It's not thread-safe (but making it such wouldn't be hard, just a different Map implementation)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants