Ktor 2.0.3 Help

Sessions

The Sessions plugin provides a mechanism to persist data between different HTTP requests. Typical use cases include storing a logged-in user's ID, the contents of a shopping basket, or keeping user preferences on the client. In Ktor, you can implement sessions by using cookies or custom headers, choose whether to store session data on the server or pass it to the client, sign and encrypt session data and more.

In this topic, we'll look at how to install the Sessions plugin, configure it, and access session data inside route handlers.

Add dependencies

To enable support for sessions, you need to include the ktor-server-sessions artifact in the build script:

implementation("io.ktor:ktor-server-sessions:$ktor_version")
implementation "io.ktor:ktor-server-sessions:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-sessions-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Install Sessions

To install the Sessions plugin, pass it to the install function in the application initialization code. Depending on the way used to create a server, this can be the embeddedServer function call ...

import io.ktor.server.application.* import io.ktor.server.sessions.* // ... fun main() { embeddedServer(Netty, port = 8080) { install(Sessions) // ... }.start(wait = true) }

... or a specified module.

import io.ktor.server.application.* import io.ktor.server.sessions.* // ... fun Application.module() { install(Sessions) // ... }

Session configuration overview

To configure the Sessions plugin, you need to perform the following steps:

  1. Create a data class: before configuring a session, you need to create a data class for storing session data.

  2. Choose how to pass data between the server and client: using cookies or a custom header. Cookies suit better for plain HTML applications, while custom headers are intended for APIs.

  3. Choose where to store the session payload: on the client or server. You can pass the serialized session's data to the client using a cookie/header value or store the payload on the server and pass only a session identifier.

    If you want to store the session payload on the server, you can choose how to store it: in the server memory or in a folder. You can also implement a custom storage for keeping session data.

  4. Protect session data: to protect sensitive session data passed to the client, you need to sign and encrypt the session's payload.

After configuring Sessions, you can get and set session data inside route handlers.

Create a data class

Before configuring a session, you need to create a data class for storing session data. For example, the UserSession class below will be used to store the session ID and the number of page views:

data class UserSession(val id: String, val count: Int)

You need to create several data classes if you are going to use several sessions.

To pass session data using cookies, call the cookie function with the specified name and data class inside the install(Sessions) block:

install(Sessions) { cookie<UserSession>("user_session") }

In the example above, session data will be passed to the client using the user_session attribute added to the Set-Cookie header. You can configure other cookie attributes by passing them inside the cookie block. For example, the code snippet below shows how to specify a cookie's path and expiration time:

install(Sessions) { cookie<UserSession>("user_session") { cookie.path = "/" cookie.maxAgeInSeconds = 10 } }

If the required attribute is not exposed explicitly, use the extensions property. For example, you can pass the SameSiteattribute in the following way:

install(Sessions) { cookie<UserSession>("user_session") { cookie.extensions["SameSite"] = "lax" } }

To learn more about available configurations settings, see CookieConfiguration.

To pass session data using a custom header, call the header function with the specified name and data class inside the install(Sessions) block:

install(Sessions) { header<CartSession>("cart_session") }

In the example above, session data will be passed to the client using the cart_session custom header. On the client side, you need to append this header to each request to get session data.

Store session payload: Client vs Server

In Ktor, you can manage the session data in two ways:

  • Pass session data between the client and server.

    If you pass only the session name to the cookie or header function, session data will be passed between the client and server. In this case, you need to sign and encrypt the session's payload to protect sensitive session data passed to the client.

  • Store session data on the server and pass only a session ID between the client and server.

    In such a case, you can choose where to store the payload on the server. For example, you can store session data in memory, in a specified folder, or you can implement your own custom storage.

Store session payload on server

Ktor allows you to store session data on the server and pass only a session ID between the server and the client. In this case, you can choose where to keep the payload on the server.

In-memory storage

SessionStorageMemory enables storing a session's content in memory. This storage keeps data while the server is running and discards information once the server stops. For example, you can store cookies in the server memory as follows:

cookie<CartSession>("cart_session", SessionStorageMemory()) { }

You can find the full example here: session-cookie-server.

Directory storage

directorySessionStorage can be used to store a session's data in a file under the specified directory. For example, to store session data in a file under the build/.sessions directory, create the directorySessionStorage in this way:

header<CartSession>("cart_session", directorySessionStorage(File("build/.sessions"))) { }

You can find the full example here: session-header-server.

Custom storage

Ktor provides the SessionStorage interface that allows you to implement a custom storage.

interface SessionStorage { suspend fun invalidate(id: String) suspend fun write(id: String, provider: suspend (ByteWriteChannel) -> Unit) suspend fun <R> read(id: String, consumer: suspend (ByteReadChannel) -> R): R }

All three functions are suspending and use ByteReadChannel and ByteWriteChannel to read and write data from/to an asynchronous channel. You can use SessionStorageMemory as a reference.

Protect session data

Sign session data

Signing session data prevents modifying a session's content but allows users see this content. To sign a session, pass a sign key to the SessionTransportTransformerMessageAuthentication constructor and pass this instance to the transform function:

install(Sessions) { val secretSignKey = hex("6819b57a326945c1968f45236589") cookie<CartSession>("cart_session", SessionStorageMemory()) { cookie.path = "/" transform(SessionTransportTransformerMessageAuthentication(secretSignKey)) } }

SessionTransportTransformerMessageAuthentication uses HmacSHA265 as the default authentication algorithm, which can be changed.

Sign and encrypt session data

Signing and encrypting session data prevents reading and modifying a session's content. To sign and encrypt a session, pass a sign/encrypt keys to the SessionTransportTransformerEncrypt constructor and pass this instance to the transform function:

install(Sessions) { val secretEncryptKey = hex("00112233445566778899aabbccddeeff") val secretSignKey = hex("6819b57a326945c1968f45236589") cookie<UserSession>("user_session") { cookie.path = "/" cookie.maxAgeInSeconds = 10 transform(SessionTransportTransformerEncrypt(secretEncryptKey, secretSignKey)) } }

By default, SessionTransportTransformerEncrypt uses the AES and HmacSHA256 algorithms, which can be changed.

Get and set session content

To set the session content for a specific route, use the call.sessions property. The set method allows you to create a new session instance:

get("/login") { call.sessions.set(UserSession(id = "123abc", count = 0)) call.respondRedirect("/user") }

To get the session content, you can call get receiving one of the registered session types as type parameter:

get("/user") { val userSession = call.sessions.get<UserSession>() }

To modify a session, for example, to increment a counter, you need to call the copy method of the data class:

get("/user") { val userSession = call.sessions.get<UserSession>() if (userSession != null) { call.sessions.set(userSession.copy(count = userSession.count + 1)) call.respondText("Session ID is ${userSession.id}. Reload count is ${userSession.count}.") } else { call.respondText("Session doesn't exist or is expired.") } }

When you need to clear a session for any reason (for example, when a user logs out), call the clear function:

get("/logout") { call.sessions.clear<UserSession>() call.respondRedirect("/user") }

You can find the full example here: session-cookie-client.

Examples

The runnable examples below demonstrate how to use the Sessions plugin:

Last modified: 28 June 2022