Handle Conversations with Sessions
A session is a mechanism to persist data between different HTTP requests. Establishing a conversationalcontext into the otherwise stateless nature of HTTP. They allow servers to keep a piece of information associatedwith the client during a sequence of HTTP requests and responses.
Different use-cases include: authentication and authorization, user tracking, keeping informationat client like a shopping cart, and more.
Sessions are typically implemented by employing Cookies
, but could also be done using headers for exampleto be consumed by other backends or an AJAX requests.
They are either client-side when the entire serialized object goes back and forth between the client and the server,or server-side when only the session ID is transferred and the associated data is stored entirely in the server.
Table of contents:
- Installation
- Usage
- Multiple sessions
- Configuration
- Deciding how to configure sessions
- Baked snippets
- Extending
This feature is defined in the class io.ktor.sessions.Sessions
and no additional artifacts are required.
Installation
Sessions are usually represented as immutable data classes (and session is changed by calling the .copy
method):
data class SampleSession(val name: String, val value: Int)
A simple Sessions installation looks like this:
install(Sessions) {
cookie<SampleSession>("COOKIE_NAME")
}
And a more advanced installation could be like:
install(Sessions) {
cookie<SampleSession>(
"SESSION_FEATURE_SESSION_ID",
directorySessionStorage(File(".sessions"), cached = true)
) {
cookie.path = "/" // Specify cookie's path '/' so it can be used in the whole site
}
}
To configure sessions you have to to specify a cookie/header name,optional server-side storage, and a class associated to the session.
If you want to further customize sessions. Please read the extending section.
Since there are several combinations for configuring sessions, there is a section about deciding how to configure sessions.
Usage
In order to access or set the session content, you have to use the call.sessions
property:
To get the session content, you can call the call.sessions.get
method receiving as type parameter oneof the registered session types:
routing {
get("/") {
// If the session was not set, or is invalid, the returned value is null.
val mySession: MySession? = call.sessions.get<MySession>()
}
}
To create or modify current session you just call the set
method of the sessions
property with the value of thedata class:
call.sessions.set(MySession(name = "John", value = 12))
To modify a session (for example incrementing a counter), you have to call the .copy
method of the data class
:
val session = call.sessions.get<MySession>() ?: MySession(name = "Initial", value = 0)
call.sessions.set(session.copy(value = session.value + 1))
When a user logs out, or a session should be cleared for any other reason, you can call the clear
function:
call.sessions.clear<MySession>()
After calling this, retrieving that session will return null, until set again.
When handling requests, you can get, set, or clear your sessions:
val session = call.sessions.get<SampleSession>() // Gets a session of this type or null if not available
call.sessions.set(SampleSession(name = "John", value = 12)) // Sets a session of this type
call.sessions.clear<SampleSession>() // Clears the session of this type
Multiple sessions
Since there could be several conversational states for a single application, you can install multiple session mappings.For example:
- Storing user preferences, or cart information as a client-side cookie.
- While storing the user login inside a file on the server.
application.install(Sessions) {
cookie<Session1>("Session1") // install a cookie stateless session
header<Session2>("Session2", sessionStorage) { // install a header server-side session
transform(SessionTransportTransformerDigest()) // sign the ID that travels to client
}
}
install(Sessions) {
cookie<SessionCart>("SESSION_CART_LIST") {
cookie.path = "/shop" // Just accessible in '/shop/*' subroutes
}
cookie<SessionLogin>(
"SESSION_LOGIN",
directorySessionStorage(File(".sessions"), cached = true)
) {
cookie.path = "/" // Specify cookie's path '/' so it can be used in the whole site
transform(SessionTransportTransformerDigest()) // sign the ID that travels to client
}
}
For multiple session mappings, both type and name should be unique.
Configuration
You can configure the sessions in several different ways:
- Where is the payload stored: client-side, or server-side.
- How is the payload or the session id transferred: Using cookies or headers.
- How are they serialized: Using an internal format, JSON, a custom engine…
- Where is the payload stored in the server: In memory, a folder, redis…
- Payload transformations: Encrypted, authenticated…
Since sessions can be implemented by various techniques, there is an extensive configuration facility to set them up:
cookie
will install cookie-based transportheader
will install header-based transport
Each of these functions will get the name of the cookie or header.
If a function is passed an argument of type SessionStorage
it will use the storage to save the session, otherwiseit will serialize the data into the cookie/header value.
Each of these functions can receive an optional configuration lambda.
For cookies, the receiver is CookieSessionBuilder
which allows you to:
- specify custom
serializer
- add a value
transformer
, like signing or encrypting - specify the cookie configuration such as duration, encoding, domain, path and so on
For headers, the receiver is HeaderSessionBuilder
which allows serializer
and transformer
customization.
For cookies & headers that are server-side with a SessionStorage
, additional configuration is identity
functionthat should generate a new ID when the new session is created.
Deciding how to configure sessions
Cookie vs Header
- Use Cookies for plain HTML applications.
- Use Header for APIs or for XHR requests if it is simpler for your http clients.
Client vs Server
- Use Server Cookies if you want to prevent session replays or want to further increase security
- Use
SessionStorageMemory
for development if you want to drop sessions after stopping the server - Use
directorySessionStorage
for production environments or to keep sessions after restarting the server
- Use
- Use Client Cookies if you want a simpler approach without the storage on the backend
- Use it plain if you want to modify it on the fly at the client for testing purposes and don’t care about the modifications
- Use it with transform authenticating and optionally encrypting it to prevent modifications
- Do not use it at all if your session payload is vulnerable to replay attacks. Security examples here.
Baked snippets
Storing the contents of the session in a cookie
Since no SessionStorage is provided as a cookie
second argument its contents will be stored in the cookie.
install(Sessions) {
val secretHashKey = hex("6819b57a326945c1968f45236589")
cookie<SampleSession>("SESSION_FEATURE_SESSION") {
cookie.path = "/"
transform(SessionTransportTransformerMessageAuthentication(secretHashKey, "HmacSHA256"))
}
}
Storing a session id in a cookie, and storing session contents in-memory
SessionStorageMemory
don’t have parameters at this point.
install(Sessions) {
cookie<SampleSession>("SESSION_FEATURE_SESSION_ID", SessionStorageMemory()) {
cookie.path = "/"
}
}
Alongside SessionStorage there is a SessionStorageMemory
class that you can use for development.It is a simple implementation that will keep sessions in-memory, thus all the sessions are droppedonce you shutdown the server and will constantly grow in memory since this implementation does not discardthe old sessions at all.
This implementation is not intended for production environments.
Storing a session id in a cookie, and storing session contents in a file
You have to include an additional artifact for the directorySessionStorage
function.
compile("io.ktor:ktor-server-sessions:$ktor_version") // Required for directorySessionStorage
install(Sessions) {
cookie<SampleSession>(
"SESSION_FEATURE_SESSION_ID",
directorySessionStorage(File(".sessions"), cached = true)
) {
cookie.path = "/" // Specify cookie's path '/' so it can be used in the whole site
}
}
As part of the io.ktor:ktor-server-sessions
artifact, there is a directorySessionStorage
functionwhich utalizes a session storage that will use a folder for storing sessions on disk.
This function has a first argument of type File
that is the folder that will store sessions (it will be createdif it doesn’t exist already).
There is also an optional cache argument, which when set, will keep a 60-second in-memory cache to preventcalling the OS and reading the session from disk each time.
Extending
Sessions are designed to be extensible, and there are some cases where you might want to further composeor change the default sessions behaviour.
For example by using custom encryption or authenticating algorithms for the transport value, or to storeyour session information server-side to a specific database.
You can define custom transformers, custom serializers and custom storages.