Skip to main content
Version: ZIO 2.x

Cookies

ZIO HTTP has special support for Cookie headers using the Cookie Domain to add and invalidate cookies. Adding a cookie will generate the correct Set-Cookie headers

A request cookie consists of name and content and can be created with Cookie.Request:

import zio._
import zio.http._

val cookie: Cookie = Cookie.Request("id", "abc")
// cookie: Cookie = Request(name = "id", content = "abc")
  • withContent updates the content of cookie
cookie.withContent("def")
// res0: Cookie = Request(name = "id", content = "def")
  • withName updates the name of cookie
cookie.withName("id2")
// res1: Cookie = Request(name = "id2", content = "abc")

A Response Cookie can be created with params name, content, expires, domain, path, isSecure, isHttpOnly, maxAge, sameSite and secret according to HTTP Set-Cookie

The below snippet creates a response cookie from the above request cookie:

val responseCookie = cookie.toResponse
// responseCookie: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = None,
// isSecure = false,
// isHttpOnly = false,
// maxAge = None,
// sameSite = None
// )

Cookie.Response is a case class so it can be updated by its copy method:

  • maxAge updates the max-age of the cookie
responseCookie.copy(maxAge = Some(5.days))
// res2: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = None,
// isSecure = false,
// isHttpOnly = false,
// maxAge = Some(value = PT120H),
// sameSite = None
// )
  • domain updates the host to which the cookie will be sent
responseCookie.copy(domain = Some("example.com"))
// res3: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = Some(value = "example.com"),
// path = None,
// isSecure = false,
// isHttpOnly = false,
// maxAge = None,
// sameSite = None
// )
  • path updates the path of the cookie
responseCookie.copy(path = Some(Root / "cookie"))
// res4: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = Some(value = Path(segments = Vector(Root, Text(text = "cookie")))),
// isSecure = false,
// isHttpOnly = false,
// maxAge = None,
// sameSite = None
// )
  • isSecure enables cookie only on https server
responseCookie.copy(isSecure = true)
// res5: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = None,
// isSecure = true,
// isHttpOnly = false,
// maxAge = None,
// sameSite = None
// )
  • isHttpOnly forbids JavaScript from accessing the cookie
responseCookie.copy(isHttpOnly = true)
// res6: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = None,
// isSecure = false,
// isHttpOnly = true,
// maxAge = None,
// sameSite = None
// )
  • sameSite updates whether or not a cookie is sent with cross-origin requests
responseCookie.copy(sameSite = Some(Cookie.SameSite.Strict))
// res7: Cookie.Response = Response(
// name = "id",
// content = "abc",
// domain = None,
// path = None,
// isSecure = false,
// isHttpOnly = false,
// maxAge = None,
// sameSite = Some(value = Strict)
// )

The cookies can be signed with a signature:

  • Using sign To sign a cookie, you can use sign
val cookie2 = Cookie.Response("key", "hello", maxAge = Some(5.days))
// cookie2: Cookie.Response = Response(
// name = "key",
// content = "hello",
// domain = None,
// path = None,
// isSecure = false,
// isHttpOnly = false,
// maxAge = Some(value = PT120H),
// sameSite = None
// )
val app = Http.collect[Request] { case Method.GET -> Root / "cookie" =>
Response.ok.addCookie(cookie2.sign("secret"))
}
// app: Http[Any, Nothing, Request, Response] = zio.http.Http$CollectHandler$$anon$17@65880400
  • Using signCookies middleware

To sign all the cookies in your HttpApp, you can use signCookies middleware:

import RequestHandlerMiddlewares.signCookies

private val app2 = Http.collect[Request] {
case Method.GET -> Root / "cookie" => Response.ok.addCookie(cookie2)
case Method.GET -> Root / "secure-cookie" => Response.ok.addCookie(cookie2.copy(isSecure = true))
}
// app2: Http[Any, Nothing, Request, Response] = zio.http.Http$CollectHandler$$anon$17@415419a4

// Run it like any simple app
def run(args: List[String]): ZIO[Any, Throwable, Nothing] =
Server.serve(app2 @@ signCookies("secret"))
.provide(Server.default)

The cookies can be added in Response headers:

val res = Response.ok.addCookie(responseCookie)
// res: Response = zio.http.Response$BasicResponse$$anon$1@d83c305

It updates the response header Set-Cookie as Set-Cookie: <cookie-name>=<cookie-value>

In HTTP requests, cookies are stored in the cookie header. cookiesDecoded can be used to get all the cookies in the request:

private val app3 = Http.collect[Request] {
case req@Method.GET -> Root / "cookie" =>
Response.text(req.header(Header.Cookie).map(_.value.toChunk).getOrElse(Chunk.empty).mkString(""))
}
// app3: Http[Any, Nothing, Request, Response] = zio.http.Http$CollectHandler$$anon$17@1784a296