akka-http-session
akka-http-session copied to clipboard
After invalidateSession I'm able to access secured endpoint with "invalidated" session
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:
- Call POST /login and receive back in the header long_encrypted_token_A
- Call GET /me with the long_encrypted_token_A header and receive back appropriate response with ADMIN value
- Call POST /logout and receive back 200 response (here I assume that the session is invalidated)
- 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
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.
@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?
Yes. That's why sessions should always have an expiry date :) Optionally refreshed with the refresh token - which assumes external storage and "global" invalidation.
I'll keep this open to clarify the docs later :)
@adamw deal :)
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
@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)