The session management system provides secure server-side storage for user data. Each user receives a unique, cryptographically-secure session ID sent via HTTP cookies.
Add the plugins to your CMakeLists.txt:
ecewo_add(
cookie@v0.2.0
session@v0.2.0
)
target_link_libraries(app PRIVATE
ecewo::ecewo
ecewo::cookie
ecewo::session
)Note
The session plugin depends on ecewo-cookie. You need both plugins to use sessions.
The session store is owned by the application: it lives in the app's plugin data
and is allocated from the app arena. Initialize it once during setup, on the
event-loop thread, before ecewo_listen():
// main.c
#include "ecewo.h"
#include "ecewo-session.h"
#include <stdio.h>
int main(void) {
ecewo_app_t *app = ecewo_create();
if (!app)
return 1;
// Initialize the session store for this app.
if (ecewo_session_init(app) != 0) {
fprintf(stderr, "Failed to initialize session system!\n");
return 1;
}
// ... register routes with ECEWO_GET(app, ...), etc. ...
ecewo_listen(app, 3000);
return 0;
}Teardown is automatic: ecewo_session_init() registers an ecewo_atexit()
callback that stops the cleanup timer and frees every session during graceful
shutdown. There is no separate cleanup call.
Initializes the per-app session store and starts the background cleanup timer.
int ecewo_session_init(ecewo_app_t *app)Parameters:
app- The application instance
Returns: 0 on success, -1 on failure
Notes:
- Call once during app setup, before
ecewo_listen()/ecewo_run(), on the event-loop thread. - Idempotent: a second call on the same app is a no-op.
- Teardown is automatic via
ecewo_atexit(). - The session store starts with a capacity of 10 sessions and doubles automatically when full.
- A background cleanup timer runs every 60 seconds to evict expired sessions.
Example:
if (ecewo_session_init(app) != 0) {
fprintf(stderr, "CRITICAL: Session initialization failed\n");
return 1;
}Creates a new session in the given app's store.
ecewo_session_t *ecewo_session_create(ecewo_app_t *app, int max_age)Parameters:
app- The application instance (useecewo_req_app(req)inside a handler)max_age- Session validity duration in seconds
Returns: Session handle or NULL on failure
Notes:
- Session IDs are 32-character URL-safe strings (
A-Z a-z 0-9 - _) generated from a cryptographically secure source. - Thread-safe.
Example:
void handle_login(ecewo_request_t *req, ecewo_response_t *res) {
// Create a session valid for 1 hour
ecewo_session_t *sess = ecewo_session_create(ecewo_req_app(req), 3600);
if (!sess) {
ecewo_send_text(res, 500, "Failed to create session");
return;
}
// ...
}Finds an active session by ID with an automatic expiry check.
ecewo_session_t *ecewo_session_find(ecewo_app_t *app, const char *id)Parameters:
app- The application instanceid- Session ID to search for
Returns: Session handle or NULL if not found or expired
Notes:
- Thread-safe.
Example:
ecewo_session_t *found = ecewo_session_find(ecewo_req_app(req), id);
if (!found) {
ecewo_send_text(res, 401, "Session expired or invalid");
return;
}Gets the session ID string.
const char *ecewo_session_id(const ecewo_session_t *sess)Parameters:
sess- Session handle
Returns: Session ID string or NULL if the session is invalid
Example:
ecewo_session_t *sess = ecewo_session_create(ecewo_req_app(req), 3600);
const char *id = ecewo_session_id(sess);Generates a new session ID while preserving data and expiry.
int ecewo_session_regenerate(ecewo_session_t *sess)Parameters:
sess- Session to regenerate
Returns: 0 on success, -1 on failure
Use case: Prevent session fixation attacks after authentication.
Notes:
- Thread-safe.
Example:
void handle_login(ecewo_request_t *req, ecewo_response_t *res) {
// ... authenticate user ...
ecewo_session_t *sess = ecewo_session_from_request(req);
if (sess) {
// Regenerate the ID after login for security
if (ecewo_session_regenerate(sess) != 0) {
ecewo_send_text(res, 500, "Session regeneration failed");
return;
}
ecewo_session_send(res, sess, NULL);
}
ecewo_send_text(res, 200, "Logged in successfully");
}Stores a key-value pair in the session.
int ecewo_session_set(ecewo_session_t *sess, const char *key, const char *value)Parameters:
sess- Target sessionkey- Key name (automatically URL-encoded)value- Value to store (automatically URL-encoded)
Returns: 0 on success, -1 on failure
Notes:
- Overwrites if the key already exists.
- Enforces a 4KB total data limit per session (measured on encoded data).
- Thread-safe.
Example:
ecewo_session_set(sess, "user_id", "12345");
ecewo_session_set(sess, "username", "john_doe");
// Special characters are handled automatically
ecewo_session_set(sess, "note", "Hello, World! 你好");Retrieves a value from the session.
char *ecewo_session_get(ecewo_session_t *sess, const char *key, ecewo_arena_t *arena)Parameters:
sess- Source sessionkey- Key to retrievearena- Arena allocator for the result, orNULLformalloc
Returns: Decoded value or NULL if not found
Memory management:
- If
arenais provided (e.g.ecewo_req_arena(req)): returns arena-allocated string, freed automatically when the response is sent. - If
arenaisNULL: returns amalloc'd string the caller must free.
Notes:
- Thread-safe.
Example:
void handle_profile(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Not logged in");
return;
}
// Arena-allocated - freed automatically with the request
char *username = ecewo_session_get(sess, "username", ecewo_req_arena(req));
if (username) {
char *response = ecewo_sprintf(ecewo_req_arena(req), "Welcome, %s!", username);
ecewo_send_text(res, 200, response);
} else {
ecewo_send_text(res, 404, "No username");
}
}Removes a key-value pair from the session.
int ecewo_session_remove(ecewo_session_t *sess, const char *key)Parameters:
sess- Target sessionkey- Key to remove
Returns: 0 on success, -1 on failure
Notes:
- Thread-safe.
Example:
void handle_remove_cart_item(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Unauthorized");
return;
}
const char *item_id = ecewo_query(req, "item");
ecewo_session_remove(sess, item_id);
ecewo_send_text(res, 200, "Item removed from cart");
}Extracts the session from the request's session cookie.
ecewo_session_t *ecewo_session_from_request(const ecewo_request_t *req)Parameters:
req- HTTP request object
Returns: Session handle or NULL if no valid session cookie
Example:
void handle_protected_route(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Authentication required");
return;
}
char *role = ecewo_session_get(sess, "role", ecewo_req_arena(req));
if (role && strcmp(role, "admin") == 0) {
ecewo_send_text(res, 200, "Admin access granted");
} else {
ecewo_send_text(res, 403, "Admin access required");
}
}Sends the session cookie to the client.
void ecewo_session_send(ecewo_response_t *res, ecewo_session_t *sess, const ecewo_cookie_options_t *options)Parameters:
res- HTTP response objectsess- Session to sendoptions- Cookie options builder (orNULLfor secure defaults)
Default cookie settings (when options is NULL):
Max-Age- Calculated from the session expiryPath-/SameSite-LaxHttpOnly- onSecure- off (enable for production HTTPS)
Example:
void handle_login(ecewo_request_t *req, ecewo_response_t *res) {
// ... authenticate user ...
ecewo_session_t *sess = ecewo_session_create(ecewo_req_app(req), 7200); // 2 hours
ecewo_session_set(sess, "user_id", "12345");
ecewo_session_set(sess, "username", "john_doe");
ecewo_cookie_options_t *opts = ecewo_cookie_options_new();
ecewo_cookie_options_set_path(opts, "/");
ecewo_cookie_options_set_same_site(opts, ECEWO_COOKIE_SAMESITE_STRICT); // CSRF protection
ecewo_cookie_options_set_http_only(opts, 1); // XSS protection
ecewo_cookie_options_set_secure(opts, 1); // HTTPS only
ecewo_session_send(res, sess, opts);
ecewo_cookie_options_free(opts);
ecewo_send_text(res, 200, "Login successful");
}Destroys the session on both server and client.
void ecewo_session_destroy(ecewo_response_t *res, ecewo_session_t *sess, const ecewo_cookie_options_t *options)Parameters:
res- HTTP response objectsess- Session to destroyoptions- Cookie options builder (orNULLfor defaults)
Actions:
- Sends the session cookie to the client — with
Max-Age=0whenoptionsisNULL(so the browser deletes it immediately), or with the caller-supplied options otherwise - Frees the session data from server memory
Note
When passing custom options, you are responsible for including Max-Age=0 (or an equivalent Expires in the past) to ensure the browser removes the cookie.
Example:
void handle_logout(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (sess) {
ecewo_session_destroy(res, sess, NULL);
}
ecewo_send_text(res, 200, "Logged out successfully");
}Frees a session from server memory only (no cookie sent).
void ecewo_session_free(ecewo_session_t *sess)Parameters:
sess- Session to free
Use case: Backend session cleanup (e.g. after database persistence).
Notes:
- Thread-safe.
Example:
void save_session_to_database(ecewo_session_t *sess) {
char *user_id = ecewo_session_get(sess, "user_id", NULL);
// ... save to database ...
free(user_id);
// Free from memory after persisting
ecewo_session_free(sess);
}#include "ecewo.h"
#include "ecewo-session.h"
#include "ecewo-cookie.h"
#include <string.h>
void handle_login(ecewo_request_t *req, ecewo_response_t *res) {
const char *username = ecewo_query(req, "username");
const char *password = ecewo_query(req, "password");
// Validate credentials (example only)
if (!username || !password ||
strcmp(username, "admin") != 0 ||
strcmp(password, "secret") != 0) {
ecewo_send_text(res, 401, "Invalid credentials");
return;
}
ecewo_session_t *sess = ecewo_session_create(ecewo_req_app(req), 3600); // 1 hour
if (!sess) {
ecewo_send_text(res, 500, "Session creation failed");
return;
}
ecewo_session_set(sess, "user_id", "1");
ecewo_session_set(sess, "username", username);
ecewo_session_set(sess, "role", "admin");
ecewo_cookie_options_t *opts = ecewo_cookie_options_new();
ecewo_cookie_options_set_path(opts, "/");
ecewo_cookie_options_set_same_site(opts, ECEWO_COOKIE_SAMESITE_STRICT);
ecewo_cookie_options_set_http_only(opts, 1);
ecewo_cookie_options_set_secure(opts, 1); // HTTPS in production
ecewo_session_send(res, sess, opts);
ecewo_cookie_options_free(opts);
ecewo_send_text(res, 200, "Login successful");
}
void handle_dashboard(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Please log in");
return;
}
char *username = ecewo_session_get(sess, "username", ecewo_req_arena(req));
char *response = ecewo_sprintf(ecewo_req_arena(req),
"<h1>Welcome, %s!</h1>", username ? username : "guest");
ecewo_send_html(res, 200, response);
}
void handle_logout(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (sess) {
ecewo_session_destroy(res, sess, NULL);
}
ecewo_send_text(res, 200, "Logged out");
}
int main(void) {
ecewo_app_t *app = ecewo_create();
if (ecewo_session_init(app) != 0) {
fprintf(stderr, "Session init failed\n");
return 1;
}
ECEWO_GET(app, "/login", handle_login);
ECEWO_GET(app, "/dashboard", handle_dashboard);
ECEWO_GET(app, "/logout", handle_logout);
ecewo_listen(app, 3000);
return 0;
}void handle_add_to_cart(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
// Create an anonymous cart session
sess = ecewo_session_create(ecewo_req_app(req), 86400); // 24 hours
if (!sess) {
ecewo_send_text(res, 500, "Failed to create cart");
return;
}
}
const char *item_id = ecewo_query(req, "item");
const char *quantity = ecewo_query(req, "qty");
if (!item_id || !quantity) {
ecewo_send_text(res, 400, "Missing item or quantity");
return;
}
char key[64];
snprintf(key, sizeof(key), "cart_%s", item_id);
ecewo_session_set(sess, key, quantity);
ecewo_session_send(res, sess, NULL);
ecewo_send_text(res, 200, "Item added to cart");
}
void handle_view_cart(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_json(res, 200, "{\"items\":[]}");
return;
}
ecewo_arena_t *arena = ecewo_req_arena(req);
char *item1_qty = ecewo_session_get(sess, "cart_101", arena);
char *item2_qty = ecewo_session_get(sess, "cart_205", arena);
char *response = ecewo_sprintf(arena,
"{\"items\":[{\"id\":101,\"qty\":%s},{\"id\":205,\"qty\":%s}]}",
item1_qty ? item1_qty : "0",
item2_qty ? item2_qty : "0");
ecewo_send_json(res, 200, response);
}void handle_privilege_escalation(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Not logged in");
return;
}
// Before granting admin privileges, regenerate the session ID.
// This prevents session fixation attacks.
if (ecewo_session_regenerate(sess) != 0) {
ecewo_send_text(res, 500, "Security update failed");
return;
}
ecewo_session_set(sess, "role", "admin");
ecewo_session_send(res, sess, NULL);
ecewo_send_text(res, 200, "Admin privileges granted");
}void handle_mfa_initiate(ecewo_request_t *req, ecewo_response_t *res) {
const char *username = ecewo_query(req, "username");
// ... verify credentials ...
// Create a temporary session for the MFA flow
ecewo_session_t *sess = ecewo_session_create(ecewo_req_app(req), 300); // 5 minutes
ecewo_session_set(sess, "mfa_pending", "true");
ecewo_session_set(sess, "username", username);
// Send the MFA code via email/SMS ...
ecewo_session_send(res, sess, NULL);
ecewo_send_text(res, 200, "MFA code sent");
}
void handle_mfa_verify(ecewo_request_t *req, ecewo_response_t *res) {
ecewo_session_t *sess = ecewo_session_from_request(req);
if (!sess) {
ecewo_send_text(res, 401, "Session expired");
return;
}
char *pending = ecewo_session_get(sess, "mfa_pending", ecewo_req_arena(req));
if (!pending || strcmp(pending, "true") != 0) {
ecewo_send_text(res, 400, "MFA not initiated");
return;
}
// ... verify the MFA code from ecewo_query(req, "code") ...
// Upgrade to a full session
ecewo_session_t *new_sess = ecewo_session_create(ecewo_req_app(req), 3600);
char *username = ecewo_session_get(sess, "username", NULL);
if (username) {
ecewo_session_set(new_sess, "username", username);
free(username);
}
ecewo_session_set(new_sess, "authenticated", "true");
// Destroy the temporary session
ecewo_session_free(sess);
ecewo_session_send(res, new_sess, NULL);
ecewo_send_text(res, 200, "MFA verified");
}