Module Webs_kit.Session

Session handling.

Sessions maintain state across request-response cycles. This module provides a basic mechanism to abstract session handlers.

A handler implementation using Authenticated_cookies for storing session state on the clients is also provided.

State descriptors

type 'a state

The type for describing session state of type 'a. Values of this type describe to session handlers how to assert values of type 'a for equality and how to codec them with bytes.

module State : sig ... end

State descriptors.

Session handler

type ('a, 'e) handler

The type for session handler with state of type 'a and state loading errors of type 'e. Values of this type are in charge of loading the state when a request is received and saving it when a response is sent back and the state has changed.

module Handler : sig ... end

Session handlers.

type 'a resp = 'a option * Webs.Resp.t

The type for session responses.

val setup : 'a state -> ('a'e) handler -> (('a option'e) Stdlib.result -> Webs.Req.t -> 'a resp) -> Webs.service

setup sd h service handles loading and saving state described by sd with handler h for service service.

The resulting service behaves as follows. On a request r, service is invoked with the state loaded by h for r. The new state returned by service is saved by h if different from the loaded state.

Client stored sessions

This handler provides sessions by storing them on the client. The session data is unencrypted but authenticated, the client cannot tamper with it – unless it finds out about your private key.

This is convenient for small user state like login status as no state needs to be maintained on the server. However be mindful about the size of your state as it travels in each request and, on state changes, in responses.

type client_stored_error = [
| Authenticated_cookie.error
| `State_decode of string(*

Session state decode errors.

*)
]

The type for client stored load state errors.

val client_stored_error_message : client_stored_error -> string

client_stored_error_message e is an english error message for e.

val client_stored_error_string : ('aclient_stored_error) Stdlib.result -> ('a, string) Stdlib.result

client_stored_error_string r is Result.map_error client_stored_error_message r.

val client_stored : private_key:Authenticatable.private_key -> ?atts:Webs.Http.Cookie.atts -> name:string -> unit -> ('aclient_stored_error) handler

client_stored ~private_key ~atts ~name stores non-expirable state on the client in an Authenticated_cookie authenticated with the private key private_key, named name and with attributes atts atts (defaults to Webs.Http.Cookie.atts_default).

Important. The default atts has no path defined. This sets the cookie (and thus the session) only for the requested URI prefix which is unlikely to be what you want. FIXME cross-check that.

Warning. The state is not encrypted and stored on the client's matchine, make sure no service secrets are part of the state.

Warning. Cookies are set in the response iff the state changes in a request-response cycle. In particular this means that using a max_age in the cookie attributes, will only refresh the deadline whenever the state changes; but we argue this should not be used anyways.

result state injection

Convenience result combinators.

val for_result : 's option -> ('a'b) Stdlib.result -> ('s option * 'a's option * 'b) Stdlib.result

for_result st r injects st in either case of r.

val for_ok : 's option -> ('a'b) Stdlib.result -> ('s option * 'a'b) Stdlib.result

for_ok st r injects st into the error case of r.

val for_error : 's option -> ('a'b) Stdlib.result -> ('a's option * 'b) Stdlib.result

for_error st r injects st into the error case of r.

type nonrec 'a result = ('a resp'a resp) Stdlib.result

Design notes

TODO. Decide whether they makes sense.

Expiring sessions

FIXME. Revise this now that we have state load errors in the service, it brings new ways around this.

The session mechanism has no provision to automatically expire sessions. In general expiring sessions is not very user friendly, especially on mobile. It seems better to let the user have its state indefinitely if it wishes and have a sudo-like mechanism that asks and provides elevated authentication for a limited amount of time to peform more sensitive operations.

This expiration mechanism can be devised on top of the mechanism of this module using session state itself – for example by having a sudo_until : Ptime.t option field in the state of an authenticated cookie and dedicated logic to handle it.

Somehow it feels like a good separation of concerns, or not.

State data structure

The state type could be a heterogenous dictionary with serializable values. This makes state highly composable, but implicitely composable and thus harder to understand and audit.

For now we would rather try to explore how inconvenient explicit state design and composition is or becomes in practice.