Skip to content

x.sessions #

Sessions

A sessions module for web projects.

Usage

The sessions module provides an implementation for session stores. The session store handles the saving, storing and retrieving of data. You can either use a store directly yourself, or you can use the session.Sessions struct which is easier to use since it also handles session verification and integrates nicely with vweb.

If you want to use session.Sessions in your web app the session id's will be stored using cookies. The best way to get started is to follow the getting started section.

Otherwise have a look at the advanced usage section.

Getting Started

The examples in this section use x.vweb. See the advanced usage section for examples without x.vweb.

To start using sessions in vweb embed sessions.CurrentSession on the Context struct and add sessions.Sessions to the app struct. We must also pass the type of our session data.

For any further example code we will use the User struct. Example:

import x.sessions
import x.vweb

pub struct User {
pub mut:
    name	 string
    verified bool
}

pub struct Context {
    vweb.Context
    // By embedding the CurrentSession struct we can directly access the current session id
    // and any associated session data. Set the session data type to `User`
    sessions.CurrentSession[User]
}

pub struct App {
pub mut:
    // this struct contains the store that holds all session data it also provides
    // an easy way to manage sessions in your vweb app. Set the session data type to `User`
    sessions &sessions.Sessions[User]
}

Next we need to create the &sessions.Sessions[User] instance for our app. This struct provides functionality to easier manage sessions in a vweb app.

Session Stores

To create sessions.Sessions We must specify a "store" which handles the session data. Currently vweb provides two options for storing session data:

  1. The MemoryStore[T] stores session data in memory only using the map datatype.
  2. The DBStore[T] stores session data in a database by encoding the session data to JSON.It will create the table DBStoreSessions in your database, to store the session data.

It is possible to create your own session store, see custom stores.

Starting the App

For this example we will use the memory store.

Example:

fn main() {
    mut app := &App{
        store: sessions.MemoryStore[User]{}
        // use your own secret which will be used to verify session id's
        secret: 'my secret'.bytes()
    }

    vweb.run[App, Context](mut app, 8080)
}

Middleware

The sessions.vweb2_middleware module provides a middleware handler. This handler will execute before your own route handlers and will verify the current session and fetch any associated session data and load it into sessions.CurrentSession, which is embedded on the Context struct.

Note: > It is recommended to use the middleware, so the sessions are always verfied > and loaded correctly.

Example:

// add this import at the top of your file
import x.sessions.vweb2_middleware

pub struct App {
    // embed the Middleware struct from vweb
    vweb.Middleware[Context]
pub mut:
    // this struct contains the store that holds all session data it also provides
    // an easy way to manage sessions in your vweb app. Set the session data type to `User`
    sessions &sessions.Sessions[User]
}

fn main() {
    mut app := &App{
        store: sessions.MemoryStore[User]{}
        // use your own secret which will be used to verify session id's
        secret: 'my secret'.bytes()
    }

    // register the sessions middleware
    app.use(vweb2_middleware.create[User, Context](mut app.sessions))

    vweb.run[App, Context](mut app, 8080)
}

You can now start using sessions with vweb!

Usage in endpoint handlers

Using Session Data

Because sessions.CurrentSession is embedded on the Context struct we can directly access any session data via ctx.session_data. This field is an option, it will be none if no data is set.

Example:

pub fn (app &App) index(mut ctx Context) vweb.Result {
    // check if a user is logged in
    if user := ctx.session_data {
        return ctx.text('Welcome ${user.name}! Verification status: ${user.verified}')
    } else {
        // user is not logged in
        return ctx.text('You are not logged in :(')
    }
}

Saving / updating session data

You can use the save method to update and save any session data.

When the user logs in, the save method is called and a new session id is generated and set as cookie. Assuming there wasn't already a session going on. If you want to be sure that a new session id is generated when you save data, you can use the resave method. This method will save the data and always set a new session id.

Example:

pub fn (mut app App) login(mut ctx Context) vweb.Result {
    // set a session id cookie and save data for the new user
    app.sessions.save(mut ctx, User{
        name: '[no name provided]'
    }) or { return ctx.server_error('could not save session data, please try again') }
    return ctx.text('You are now logged in!')
}

The following endpoint checks if a session exists, if it doesn't inform the user that they need to login.

If a session does exists the users name is updated to the name query parameter, you can use this route via http://localhost:8080/save?name=myname. And if the query parameter is not passed an error 400 (bad request) is returned.

Example:

pub fn (mut app App) save(mut ctx Context) vweb.Result {
    // check if there is a session
    app.sessions.get(ctx) or { return ctx.request_error('You are not logged in :(') }

    if name := ctx.query['name'] {
        // update the current user
        app.sessions.save(mut ctx, User{
            name: name
        }) or { return ctx.server_error('could not save session data, please try again') }
        return ctx.redirect('/', typ: .see_other)
    } else {
        // send HTTP 400 error
        return ctx.request_error('query parameter "name" must be present!')
    }
}

Destroying data / logging out

If a user logs out you can use the logout method to destroy the session data and clear the session id cookie. If you only want to destroy the session data use the destroy method.

Example:

pub fn (mut app App) logout(mut ctx Context) vweb.Result {
    app.sessions.logout(mut ctx) or { return ctx.server_error('could not logout, please try again') }
    return ctx.text('You are now logged out!')
}

Configuration

Change the cookie_options field to modify how the session cookie is stored.

Example:

mut app := &App{
    sessions: &sessions.Sessions[User]{
        // ...
        cookie_options: sessions.CookieOptions{
            // cookie can only be stored on an HTTPS site.
            secure: true
        }
    }
}

Mag-age

By default the expiration date of a session is 30 days. You can change this by setting the max_age field. If max_age = 0, then session expiration times are not checked and sessions will be stored forever until they are destroyed.

Pre-sessions

By default a session cookie is only generated when you call save, or resave. By setting save_uninitialized to true a session cookie will always be set, even if there is no data for the session yet. This is useful when you need session data to be always available.

Or, for example, you could use pre-sessions to mitigate login-csrf, since you can bind a csrf-token to the "pre-session" id. Then when the user logs in, you can set a new session id with resave..

Advanced Usage

If you want to store session id's in another manner than cookies, or if you want to use this sessions module outside of vweb, the easiest way is to create an instance of a Store and directly interact with it.

First we create an instance of the MemoryStore and pass the user struct as data type. Example:

import x.sessions

const secret = 'my secret'.bytes()

pub struct User {
pub mut:
    name     string
    verified bool
}

fn main() {
    mut store := sessions.MemoryStore[User]{}

    user := User{
        name: 'vaesel'
    }
}

Generating and validating session id's

The session module provides a function for generating a new signed session id and for verifying a signed session id. You can ofcourse generate your own session id's.

Example:

// fn main
// generate a new session id and sign it
session_id, signed_session_id := sessions.new_session_id(secret)
// save session data to our store
store.set(session_id, user)!

// get a normal session id from the signed version and verify it
verified_session_id, valid := sessions.verify_session_id(signed_session_id, secret)
assert verified_session_id == session_id && valid == true

We can retrieve the saved user and verify that the data we saved can be retrieved from the verified session id.

Example:

// fn main
// pass `max_age = 0` to ignore the expiration time.
if saved_user := store.get(verified_session_id, 0) {
    assert user == saved_user
    println('Retrieved a valid user! ${saved_user}')
} else {
    println(':(')
}

Custom Stores

You can easily create your own custom store in order to control how session data is stored and retrieved. Each session store needs to implement the Store[T] interface.

pub interface Store[T] {
mut:
    // get the current session data if the id exists and if it's not expired.
    // If the session is expired, any associated data should be destroyed.
    // If `max_age=0` the store will not check for expiration of the session.
    get(sid string, max_age time.Duration) !T
    // destroy session data for `sid`
    destroy(sid string) !
    // set session data for `sid`
    set(sid string, val T) !
}

// get data from all sessions, optional to implement
pub fn (mut s Store) all[T]() ![]T {
    return []T{}
}

// clear all session data, optional to implement
pub fn (mut s Store) clear[T]() ! {}

Only the get, destroy and set methods are required to implement.

Session Expire time

The max_age argument in get can be used to check whether the session is still valid. The database and memory store both check the expiration time from the time the session data first inserted. But if max_age = 0, the stores will not check for expiration time.

fn new_session_id #

fn new_session_id(secret []u8) (string, string)

new_session_id creates and returns a random session id and its signed version. You can directly use the signed version as a cookie value

fn verify_session_id #

fn verify_session_id(raw_sid string, secret []u8) (string, bool)

verify_session_id verifies the signed session id with secret. This function returns the session id and if the session id is valid

fn DBStore.create #

fn DBStore.create[T](db orm.Connection) !DBStore[T]

create a new Database store with a connection to a database

interface Store #

interface Store[T] {
mut:
	// get the current session data if the id exists and if it's not expired
	get(sid string, max_age time.Duration) !T
	// destroy session data for `sid`
	destroy(sid string) !
	// set session data for `val`
	set(sid string, val T) !
}

fn (Store) all #

fn (mut s Store) all[T]() ![]T

get data from all sessions, optional to implement

fn (Store) clear #

fn (mut s Store) clear[T]() !

clear all session data, optional to implement

fn (DBStore[T]) all #

fn (mut store DBStore[T]) all() ![]T

all gets the data from all sessions

fn (DBStore[T]) get #

fn (mut store DBStore[T]) get(sid string, max_age time.Duration) !T

get session for session id sid. The session can be max_age old. max_age will be ignored when set to 0

fn (DBStore[T]) destroy #

fn (mut store DBStore[T]) destroy(sid string) !

destroy data for session id sid

fn (DBStore[T]) clear #

fn (mut store DBStore[T]) clear() !

clear all sessions

fn (DBStore[T]) set #

fn (mut store DBStore[T]) set(sid string, val T) !

set session data for session id sid

fn (MemoryStore[T]) all #

fn (mut store MemoryStore[T]) all() ![]T

get data from all sessions

fn (MemoryStore[T]) get #

fn (mut store MemoryStore[T]) get(sid string, max_age time.Duration) !T

get session for session id sid. The session can be max_age old. max_age will be ignored when set to 0

fn (MemoryStore[T]) destroy #

fn (mut store MemoryStore[T]) destroy(sid string) !

destroy data for session id sid

fn (MemoryStore[T]) clear #

fn (mut store MemoryStore[T]) clear() !

clear all sessions

fn (MemoryStore[T]) set #

fn (mut store MemoryStore[T]) set(sid string, val T) !

set session data for session id sid

fn (Sessions[T]) set_session_id #

fn (mut s Sessions[T]) set_session_id[X](mut ctx X) string

set_session_id generates a new session id and set a Set-Cookie header on the response

fn (Sessions[T]) validate_session #

fn (mut s Sessions[T]) validate_session[X](ctx X) (string, bool)

validate_session validates the current session, returns the session id and the validation status

fn (Sessions[T]) get #

fn (mut s Sessions[T]) get[X](ctx X) !T

get the data associated with the current session, if it exists

fn (Sessions[T]) destroy #

fn (mut s Sessions[T]) destroy[X](mut ctx X) !

destroy the data for the current session

fn (Sessions[T]) logout #

fn (mut s Sessions[T]) logout[X](mut ctx X) !

logout destroys the data for the current session and removes the session id Cookie

fn (Sessions[T]) save #

fn (mut s Sessions[T]) save[X](mut ctx X, data T) !

save data for the current session

fn (Sessions[T]) resave #

fn (mut s Sessions[T]) resave[X](mut ctx X, data T) !

resave saves data for the current session and reset the session id. You should use this function when the authentication or authorization status changes e.g. when a user signs in or switches between accounts/permissions. This function also destroys the data associated to the old session id.

fn (Sessions[T]) get_session_id #

fn (s &Sessions[T]) get_session_id[X](ctx X) ?string

get_session_id retrieves the current session id, if it is set

struct CookieOptions #

struct CookieOptions {
pub:
	cookie_name string = 'sid'
	domain      string
	http_only   bool          = true
	path        string        = '/'
	same_site   http.SameSite = .same_site_strict_mode
	secure      bool
}

CookieOptions contains the default settings for the cookie created in the Sessions struct.

struct CurrentSession #

struct CurrentSession[T] {
pub mut:
	session_id   string
	session_data ?T
}

CurrentSession contains the session data during a request. If you use x.vweb you could embed it on your Context struct to have easy access to the session id and data.

Example

pub struct Context {
    vweb.Context
    sessions.CurrentSessions[User]
}

struct DBStore #

@[noinit]
struct DBStore[T] {
pub mut:
	db orm.Connection @[required]
}

DBStore stores sessions in a database

struct DBStoreSessions #

struct DBStoreSessions {
pub mut:
	session_id string    @[primary]
	created_at time.Time
	data       string
}

DBStoreSessions is the table that is created in your database and represents a session data record

struct MemoryStore #

struct MemoryStore[T] {
mut:
	data map[string]MemoryStoreSessions[T]
}

MemoryStore stores sessions in a map in memory only.

struct Sessions #

@[heap]
struct Sessions[T] {
pub:
	secret         []u8          @[required]
	cookie_options CookieOptions
	// max age of session data and id, default is 30 days
	max_age time.Duration = time.hour * 24 * 30
	// set to true if you want to create a session if there isn't any data stored yet.
	// Also called pre-sessions
	save_uninitialized bool
pub mut:
	store Store[T] @[required]
}

Sessions can be used to easily integrate sessions with x.vweb. This struct contains the store that holds all session data it also provides an easy way to manage sessions in your vweb app.

Example

pub struct App {
pub mut:
    sessions &sessions.Sessions[User]
}