Cookies and Sessions

Finch provides two mechanisms for persisting data between HTTP requests: cookies (client-side) and sessions (server-side). Understanding the difference helps you choose the right tool:

  • Cookies are stored in the browser and sent with every request. They are suitable for lightweight user preferences (theme, language) that can be safely stored client-side.
  • Sessions are stored on the server. Only a session ID is sent to the browser. They are suitable for sensitive data such as the currently logged-in user.

Both are accessible through the rq object inside any controller or route handler.

Cookies

Cookies are key-value string pairs stored in the browser. Finch reads them from incoming Cookie headers and writes them via Set-Cookie response headers.

Use rq.getCookie(key, def: defaultValue) to read a cookie. The def parameter specifies what to return if the cookie does not exist.

safe defaults to true on both getCookie and addCookie. Unless you say otherwise, every cookie Finch reads or writes is transparently encrypted/decrypted. To store or read a genuinely plain-text value (readable in the browser's dev tools), you must pass safe: false explicitly:

// Encrypted (default) — decrypted automatically when read back with getCookie
var token = rq.getCookie('auth_token', def: '');

// Plain — value is stored and returned exactly as given
var theme = rq.getCookie('theme', def: 'light', safe: false);

Use rq.addCookie(key, value) to set a cookie. The cookie is attached to the response:

// Encrypted (default)
rq.addCookie('auth_token', 'abc123');

// Plain
rq.addCookie('theme', 'dark', safe: false);

addCookie also accepts finer-grained options for the underlying Set-Cookie header:

rq.addCookie(
  'session_pref',
  'compact',
  safe: false,
  duration: Duration(days: 30), // sets Max-Age
  path: '/',                    // default is '/'
  domain: '.example.com',
  secure: true,                 // only send over HTTPS
  httpOnly: true,                // not readable from JavaScript
  sameSite: SameSite.lax,
);

To remove a cookie, Finch re-sets it with an expired max-age:

rq.removeCookie('theme');

Complete Example

class CookieController extends Controller {
  Future<String> showCookie() async {
    var value = rq.getCookie('test', def: 'not set', safe: false);
    rq.addParam('cookieValue', value);
    return rq.renderView(path: 'example/cookie');
  }

  Future<String> addCookie() async {
    var key   = rq.get<String>('key', def: '');
    var value = rq.get<String>('value', def: '');
    var safe  = rq.get<bool>('safe', def: false);

    if (key.isNotEmpty) {
      rq.addCookie(key, value, safe: safe);
    }
    return rq.redirect('/example/cookie');
  }
}

Sessions

Sessions are managed by Dart's built-in HttpSession. Each visitor receives a unique session ID stored in a browser cookie, but the actual data lives on the server in memory.

Important: The default Dart HttpSession is memory-backed. Session data is lost when the server restarts, and is not shared across multiple server instances behind a load balancer. For production use at scale, consider a persistent/shared session store or store critical data in the database instead.

Read a Session Value

Use rq.getSession(key, def: defaultValue) to read a value. It returns def if the key does not exist. Note the return type is Object, not String — cast it if you need a specific type:

var userEmail = rq.getSession('user', def: '') as String;

Write a Session Value

Use rq.addSession(key, value) to store a value in the current session. The value can be any JSON-friendly type (String, num, bool, Map, List), not just strings:

rq.addSession('user', '[email protected]');
rq.addSession('cart', {'items': 3, 'total': 42.5});

Remove a Session Value

rq.session.remove('user');

Complete Example

A typical login/logout/dashboard flow using sessions:

class SessionController extends Controller {
  Future<String> login() async {
    var email    = rq.get<String>('email', def: '');
    var password = rq.get<String>('password', def: '');

    if (isValidUser(email, password)) {
      // Store the user's email in the session after successful authentication
      rq.addSession('user', email);
      return rq.redirect('/dashboard');
    }
    return rq.renderError(401);
  }

  Future<String> logout() async {
    // Remove the user from the session to log them out
    rq.session.remove('user');
    return rq.redirect('/login');
  }

  Future<String> dashboard() async {
    var email = rq.getSession('user', def: '') as String;

    // If no session, the user is not logged in — redirect to login
    if (email.isEmpty) return rq.redirect('/login');

    rq.addParam('email', email);
    return rq.renderView(path: 'pages/dashboard');
  }
}

For a full authentication flow with permissions and route guarding, see Auth Controller — it builds on the same getSession/addSession calls shown here.

The encryption key is FinchConfigs.cookiePassword:

FinchConfigs configs = FinchConfigs(
  cookiePassword: env.get('COOKIE_SECRET', 'change-this-in-production'),
);
// Writing — stored encrypted in the browser
rq.addCookie('prefs', '{"theme":"dark"}');

// Reading back — Finch decrypts it automatically
var prefs = rq.getCookie('prefs', def: '{}');

Because encryption is keyed to cookiePassword, changing that value invalidates every cookie already stored in visitors' browsers — getCookie will fail to decrypt them and silently fall back to def.

Security: Use a long, random cookiePassword. Store it as an environment variable. Never hardcode it or commit it to version control.

Reading Cookies in Templates

You can also read a cookie value directly inside a Jinja template:

{{ $e.getCookie('theme', 'light') }}

See Template for everything else available on $e inside a template.