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:
- The
MemoryStore[T]
stores session data in memory only using themap
datatype. - The
DBStore[T]
stores session data in a database by encoding the session data to JSON.It will create the tableDBStoreSessions
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 #
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 #
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]
}