Working with Session
What is session?
HTTP protocol is stateless protocol. It does not have built-in mechanism to maintain state between each request.
Session is mechanism which store request state in server. State is identified by some random unique id. Each request and response will exchange this identifier to maintain state between requests.
To use session in Fano Framework, you need to use
ISessionManager and also dispatcher instance which support session. Fano Framework provides built-in implementation for this interface.
TFileSessionManager, session manager which store session data in file.
TCookieSessionManager, session manager which store session data in encrypted cookie.
TDbSessionManager, session manager which store session data in RDBMS database.
This interface is provided for getting session instance from a request. This interface is parent of
ISessionManager interface and only has one method
getSession() which expects
IRequest instance and returns
Create session manager instance
Easiest steps to work with session is to scaffold Fano Framework web application project with session support using Fano CLI.
Store session data in file
TFileSessionManager instance to dependency container, Fano Framework provides
TIniFileSessionManagerFactory classes which will create session manager which store its data as JSON and INI file respectively.
Its constructor accepts three optional parameters:
- Name of session (default value of
FANOSESSID). This will be used as name of cookie.
- Path of directory where files will be stored (default value of
- Prefix which will be prepended before session id (default value of empty string).
You need to make sure that session directory is writeable by application.
var sessionMgrFactory : IDependencyFactory; ... sessionMgrFactory := TJsonFileSessionManagerFactory.create( 'fano_sess`, '/home/fanoapp/storages/sessions/' );
or you can create factory with default value
sessionMgrFactory := TJsonFileSessionManagerFactory.create();
If it is not set, by default, code above will use GUID as session id (using
TGuidSessionIdGeneratorFactory class). Read Session ID generator for more information on algorithm used to generate session identifier.
After that register factory to dependency container
Store session data in cookie
Fano Framework can store session data as encrypted cookie instead. This has advantages:
- Reduce disk usage compared to store session data in file. Session data is stored in user client browser cookies.
- Simplify session management when using load balancer running multiple application instances.
However, it has drawback too:
- A single cookie have limit of 4KB in size. You cannot store too many data and the size of actual unencrypted data may be less due to usage of base64 encoding.
To store session data in encrypted cookie value, you need to use
TCookieSessionManager. You also need to create instance of
IDecrypter interface which responsible to encrypt and decrypt cookie value. Fano Framework provides built-in encrypter using Blowfish algorithm.
Following code show how to create session manager which store session data in encrypted cookie. You need to setup a random secure secret key to be used to encrypt and decrypt.
container.add( 'encrypter', TBlowfishEncrypterFactory.create() .secretKey( config.getString('secretKey') ) ); container.add( 'sessionManager', TCookieSessionManagerFactory.create( TJsonSessionFactory.create(), container['encrypter'] as IEncrypter, container['encrypter'] as IDecrypter, config.getString('session.name') ) );
Code above will internally store data as JSON format, to use INI format, just replace
You can replace
TBlowfishEncrypterFactory above with
TMd5BlowfishEncrypterFactory which adds encrypted cookie integrity check using HMAC SHA1 or HMAC MD5 respectively. When encrypted cookie is tampered, integrity check will fail and in turn, new session is created.
container.add( 'encrypter', TSha1BlowfishEncrypterFactory.create() .secretKey( config.getString('secretKey') ) );
Fourth parameter of constructor method of
TCookieSessionManagerFactory is optional session name parameter with default value of
FANOSESSID. So you can omit and use default value as follows.
container.add( 'sessionManager', TCookieSessionManagerFactory.create( TJsonSessionFactory.create(), container['encrypter'] as IEncrypter, container['encrypter'] as IDecrypter ) );
See Fano Session Cookie, example web project to demonstrate how to use session that store its data in encrypted cookie.
Store session data in database
To use database for session storage, you need to create a table which contain at least three columns which will store session id, serialized session data and session expiry. Two first columns should be string type column, such as
VARCHAR and last column should be
DATETIME. For best performance, session id column should be primary key or at least unique index.
You need to make sure that you create proper database credential which has
DELETE privilege on table mentioned above.
TDbSessionManager class provides capability to manage session data in database.
To register it to dependency container, Fano Framework provides
TIniDbSessionManagerFactory classes that will create session manager having capability to store its data in database as serialized JSON and INI string respectively.
Its constructor accepts two parameters, instance of
IRdbms interface and name of session (optional with default value of
It provides several additional methods to let Fano Framework knows about your table schema. Read database documentation for information on working with database in Fano Framework.
var sessionMgrFactory : IDependencyFactory; ... sessionMgrFactory := TJsonDbSessionManagerFactory.create( container['db'] as IRdbms, 'fano_sess` ).table('fano_sessions') .sessionIdColumn('id') .dataColumn('data') .expiredAtColumn('expired_at'); container.add('sessionManager', sessionMgrFactory);
If table schema is not defined, factory class will assume table name
fano_sessions with three columns:
expired_at. So following code is doing same thing as above.
sessionMgrFactory := TJsonDbSessionManagerFactory.create( container['db'] as IRdbms, 'fano_sess` );
You can omit last parameter and use default value as follows.
sessionMgrFactory := TJsonDbSessionManagerFactory.create( container['db'] as IRdbms );
Fano Framework allows developer to change how session identifiers are generated. The idea is to minimise the probability of generating two session IDs with the same value.
You can change the way session identifiers are generated by calling
sessionIdGenerator() of session manager factory and pass factory class of
ISessionIdGeneratorFactory interface, as shown in following code,
sessionMgrFactory := TJsonFileSessionManagerFactory.create( 'fano_sess`, '/home/fanoapp/storages/sessions/' ).sessionIdGenerator( TKeyGuidSessionIdGeneratorFactory.create('some random string as secret key') );
All session manager factory classes have
sessionIdGenerator() method so you can also set session id generator on, for example,
sessionMgrFactory := TJsonDbSessionManagerFactory.create( container['db'] as IRdbms, 'fano_sess` ).sessionIdGenerator( TKeyGuidSessionIdGeneratorFactory.create('some random string as secret key') ).table('fano_sessions') .sessionIdColumn('sess_id') .dataColumn('sess_data') .expiredAtColumn('expired_at');
If you need to implement your own session id generator, you need to implement
ISessionIdGenerator interface and also create its factory class which implements
Built-in session id generator
TGuidSessionIdGeneratorFactoryis built-in factory class which will create session id generator which use GUID. While GUID is unique, it is predictable so it is vulnerable to session hijack attack.
TKeyGuidSessionIdGeneratorFactoryis built-in factory class which will create session id which use SHA1 hash of a secret key concatenated with GUID as session id.
TIpKeyGuidSessionIdGeneratorFactoryis built-in factory class which will create session id which use SHA1 hash of concatenated string of client IP address + time + secret key + GUID as session id.
TKeyRandSessionIdGeneratorFactoryis built-in factory class which will create session id generator which use SHA1 hash of a secret key + client IP address + time + random bytes from
TSha2KeyRandSessionIdGeneratorFactoryis similar to
TKeyRandSessionIdGeneratorFactoryexcept that it uses SHA2 256-bit hash.
TGuidSessionIdGeneratorFactory which its constructor does not require parameter, other built-in factory classes expect secret key to be provided when creating factory class.
It is strongly advised that you use cryptographically strong random secret key. Fano CLI can help generate random secret key for you.
sessionMgrFactory := TJsonFileSessionManagerFactory.create( 'fano_sess`, '/home/fanoapp/storages/sessions/' ).sessionIdGenerator( TGuidSessionIdGeneratorFactory.create() );
or with more cryptographically strong session id generator
sessionMgrFactory := TJsonFileSessionManagerFactory.create( 'fano_sess`, '/home/fanoapp/storages/sessions/' ).sessionIdGenerator( TSha2KeyRandSessionIdGeneratorFactory.create('your very secret hush hush key') );
Session initialization and serialization
To be able to keep track of client request, session data needs to be initialized each time request is coming and it needs to be serialized and persisted in storage when response is about to get sent over wire.
There is two mechanism where session initialization and serialization can be done, i.e via dispatcher and middleware.
When using dispatcher for working with session, session is initialized after request object is created and serialized to storage before response is sent to client over the wire. So session is always applied globally through out application.
When using middleware for working with session, session is initialized when middleware, which is attached globally to application middleware list or per route, is executed. Using middleware when working with session is more flexible. For example you may want to apply session management only for certain routes and not other.
Create dispatcher instance which support session
TSessionDispatcherFactory is factory which can create dispatcher which support session.
It expects 6 parameters:
- Instance of global middleware collection.
- Instance of router matcher.
- Request response factory
- Instance of
- Instance of
- Integer value of cookie max age in seconds
container.add( GuidToString(IDispatcher), TSessionDispatcherFactory.create( container.get('appMiddlewares') as IMiddlewareLinkList, container.get(GuidToString(IRouteMatcher)) as IRouteMatcher, TRequestResponseFactory.create(), container.get('sessionManager') as ISessionManager, (TCookieFactory.create()).domain('your.app.example.com'), cookieMaxAge ) );
Please read Dispatcher for more information or you may want to get information about how to create Fano web application project with session using Fano CLI.
Since v1.10.0, Fano Framework provides session middleware
TSessionMiddleware which can be use, as an alternative to using dispatcher when working with session.
TSessionMiddlewareFactory class is factory class for this middleware.
container.add( 'my.session.middleware', TSessionMiddlewareFactory.create( container['sessionManager'] as ISessionManager ) );
To apply session middleware globally, so that session is initialized and serialized through out application.
(container['appMiddlewares'] as IMiddlewareList) .add(container['my.session.middleware'] as IMiddleware);
It is similar to using dispatcher.
To apply session middleware so that session is only initialized and serialized when certain routes is accessed, attach middleware to route.
router.get('/', container['myHomeCtrl'] as IRequestHandler) .add(container['my.session.middleware'] as IMiddleware); router.get('/account', container['myAccountCtrl'] as IRequestHandler) .add(container['my.session.middleware'] as IMiddleware); router.get('/staticfile.pdf', container['myFileCtrl'] as IRequestHandler);
Code above cause session to be initialized and persisted when route ‘/’ and ‘/account’ is accessed but not when ‘/staticfile.pdf’ route is accessed.
Injecting session manager instance to controller or middleware
type TAuthOnlyMiddleware = class(TInjectableObject, IMiddleware) private fSesionManager : IReadOnlySessionManager; ... public constructor create(const session : IReadOnlySessionManager); ... end; ... implementation constructor TAuthOnlyMiddleware.create(const session : IReadOnlySessionManager); begin inherited create(); fSession := session; end; ... end.
Get session instance
From inside controller or middleware, you can get
ISession instance from request using
getSession() method of
function TAuthOnlyMiddleware.handleRequest( const request : IRequest; const response : IResponse; const args : IRouteArgsReader; const next : IRequestHandler ) : IResponse; var sess : ISession; begin sess := fSession.getSession(request); //do something with session instance end;
After you hold
ISession instance, you can query session state using its method.
You can also use simplified syntax,
sess := fSession[request];
Test if session variable is set
var signedIn : boolean; ... signedIn := sess.has('userSignedIn');
Read session variable value
var userName : string; ... userName := sess.getVar('username');
ESessionExpired exception if session is expired.
You can also use simplified syntax
userName := sess['username'];
Write session variable value
ESessionExpired exception if session is expired.
You can also use simplified syntax
sess['username'] := 'firstname.lastname@example.org';
Delete session variable
Delete all session variables
Test if session is expired
ESessionExpired exception when reading a value, you can test if current session is expired or not.
var userName : string; ... if (not sess.expired()) then begin userName := sess.getVar('username'); end;
Get expiration datetime
To get time when session is expired
var expiresDate : TDatetime; ... expiresDate := sess.expiresAt();
Get current session id
To get current session id
var sessionId : string; ... sessionId := sess.id();
Get session name
To get session name,
var sessionName : string; ... sessionName := sess.name();
If you create session manager factory as example above,
sessionName will contains
- Example applications
- Session example applications
- Session in cookie example applications
- Session in database example