After invalidateSession I'm able to access secured endpoint with "invalidated" session
Fruzenshtein opened this issue · comments
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 :)
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)