tokumei v0.6.4 Tokumei.Session.SignedCookies

Cookie based session storage.

Tokumei.Session.SignedCookies can be added as a middleware.

A session is a map of binary keys and values. A session added to a response under the tokumei-session header will be sent to the client. A valid session sent from the client is added to the request under the tokumei-session header.

defmodule MyApp do
  use Tokumei.NotFound
  use Tokumei.Router
  import Raxx.Response
  use Tokumei.Session.SignedCookies

  @secret "eggplant"

  route ["session", first_value] do
    :PUT ->
      new_session = %{"first" => first_value}
      ok("Session set", [{"tokumei-session", new_session}])
  end

  route ["session", first_value, second_value] do
    :PUT ->
      new_session = %{"first" => first_value, "second" => second_value}
      ok("Session set", [{"tokumei-session", new_session}])
  end

  route ["session"], request do
    :GET ->
      session = :proplists.get_value("tokumei-session", request.headers)
      value = Map.get(session, "first", "none-set")
      ok("Session value: #{value}")
    :DELETE ->
      new_session = %{}
      ok("Session set", [{"tokumei-session", new_session}])
  end
end

Responses will have any session data that is set serialized into cookies. Requests will have their session (if any) rebuilt from the cookies they send.

An additional cookie (tokumei.session) is set to validate the integrity of sessions.

N.B. Stored session data can be viewed by the client.

There are also limitations to the number and size of cookies that a browser will persist.

ALSO, should cookie attribute be captialized Path=/ or path=/

Summary

Functions

Expire the session

Set cookies for a sessions contents

Extract session from signed cookie values

Functions

delete(map, keys, opts)

Expire the session.

Expires a session by resetting the validation cookie with a past expiry date. A list of keys will also be expired, to remove any user preferences from the browser.

# Expire the signature cookie.
iex> Response.ok()
...> |> SignedCookies.delete([], [])
...> |> Map.get(:headers)
...> |> List.last()
{"set-cookie",
  "tokumei.session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; max-age=0; HttpOnly"}

# Expire the cookie for each key.
iex> Response.ok()
...> |> SignedCookies.delete(["foo"], [])
...> |> Map.get(:headers)
...> |> List.first()
{"set-cookie",
  "foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; max-age=0; HttpOnly"}
embed(map, session, opts)

Set cookies for a sessions contents

Examples

# Each key value from a session is set as a cookie
iex> Response.ok()
...> |> SignedCookies.embed(%{"foo" => "bar"}, secret: "secret")
...> |> Map.get(:headers)
...> |> List.first
{"set-cookie", "foo=bar; path=/; HttpOnly"}

# A signature is set as a final cookie
iex> Response.ok()
...> |> SignedCookies.embed(%{"foo" => "bar"}, secret: "secret")
...> |> Map.get(:headers)
...> |> List.last
{"set-cookie",
  "tokumei.session=foo -- yjdW%2BB03KKAjvcimIsCQbzc7eV4%3D; path=/; HttpOnly"}
extract(request, opts)

Extract session from signed cookie values.

Examples

# Sessions will be extracted from cookies
iex> Request.get("/", [
...>   {"cookie", "foo=bar"},
...>   {"cookie", "tokumei.session=foo -- yjdW%2BB03KKAjvcimIsCQbzc7eV4%3D"}])
...> |> SignedCookies.extract(secret: "secret")
%{"foo" => "bar"}

# Extra cookies are discarded
iex> Request.get("/", [
...>   {"cookie", "foo=bar"},
...>   {"cookie", "stealthy=value"},
...>   {"cookie", "tokumei.session=foo -- yjdW%2BB03KKAjvcimIsCQbzc7eV4%3D"}])
...> |> SignedCookies.extract(secret: "secret")
%{"foo" => "bar"}

# Missing value will result in no session
iex> Request.get("/", [
...>   {"cookie", "tokumei.session=foo -- yjdW%2BB03KKAjvcimIsCQbzc7eV4%3D"}])
...> |> SignedCookies.extract(secret: "secret")
%{}
# Extend {:error, {:missing_keys: ["foo"]}}

# Tampered values will return empty session
iex> Request.get("/", [
...>   {"cookie", "foo=BAD"},
...>   {"cookie", "tokumei.session=foo -- yjdW%2BB03KKAjvcimIsCQbzc7eV4%3D"}])
...> |> SignedCookies.extract(secret: "secret")
%{}
# Extend {:error, :signature_does_not_match}

# Invalid session cookie will return empty session
iex> Request.get("/", [
...>   {"cookie", "foo=bar"},
...>   {"cookie", "tokumei.session=garbage"}])
...> |> SignedCookies.extract(secret: "secret")
%{}
# Extend {:error, :invalid_session_hallmark}

# Sessions will be extracted from cookies
iex> Request.get("/", [
...>   {"cookie", "foo=bar"}])
...> |> SignedCookies.extract(secret: "secret")
%{}
# Extend {:error, :no_session_hallmark}