Authentication and Authorization
Sessions
Section titled “Sessions”Ecewo offers some session management APIs for authentication and authorization:
create_session()
to create a sessionfind_session()
to find a session in memoryget_session()
to get the session from requestfree_session()
to delete the session only from memorysend_session()
to send the session to the client as cookiedelete_sesion()
to delete the session both from the client and the memoryprint_sessions()
to print all the active sessions
With the power of these APIs, we can easily manage the authentication and authorization.
Let’s make an authentication example and see how it works.
First, we need to install the session.c
and session.h
files from Plugins and add them to our project.
Let’s write a login
handler:
#ifndef HANDLERS_H#define HANDLERS_H
#include "ecewo.h"
void handle_login(Req *req, Res *res);
#endif
#include "handlers.h"#include "cJSON.h"#include "session.h"
// Example user info (it must be saved in a database)const char *STATIC_NAME = "John Doe";const char *STATIC_USERNAME = "johndoe";const char *STATIC_PASSWORD = "123123";const int STATIC_USER_ID = 1;
void handle_login(Req *req, Res *res){ const char *body = req->body; if (!body) { send_text(400, "Missing request body"); return; }
cJSON *json = cJSON_Parse(body); if (!json) { send_text(400, "Invalid JSON"); return; }
const char *username = cJSON_GetObjectItem(json, "username")->valuestring; const char *password = cJSON_GetObjectItem(json, "password")->valuestring;
if (!username || !password) { cJSON_Delete(json); send_text(400, "Missing fields"); return; }
if (strcmp(username, STATIC_USERNAME) == 0 && strcmp(password, STATIC_PASSWORD) == 0) { // Create session on RAM for 1 hour Session *session = create_session(3600);
// Set key-value variables to the session set_session(session, "name", STATIC_NAME); set_session(session, "username", STATIC_USERNAME); set_session(session, "theme", "dark");
// Send the session as cookie for 1 hour send_session(res, session);
printf("Session ID: %s\n", sid); printf("Session JSON: %s\n", sess->data);
send_text(200, "Login successful!"); } else { send_text(401, "Invalid username or password"); }
cJSON_Delete(json);}
#include "server.h"#include "handlers/handlers.h"#include "session.h"
int main(){ init_router(); init_sessions();
post("/login", handle_login);
ecewo(3000);
reset_sessions(); reset_router();
return 0;}
Let’s send a request to http://localhost:3000/login
with that body:
{ "username": "johndoe", "password": "123123"}
If login is successful, we’ll see a “Login successful!” response and a header like "Cookie": "session_id=VKdbMRbqMhh_40F6ef2FreEba6JqkH16"
will be added to the headers.
Logout
Section titled “Logout”We also write a logout handler to use after login. Let’s add these parts:
// Add this handler:
void handle_logout(Req *req, Res *res){ // First, check if the user has session Session *session = get_session(&req->headers);
if (!session) { send_text(400, "You have to login first"); } else { delete_session(res, session); send_text(302, "Logged out"); }}
Declare the logout handler too:
#ifndef HANDLERS_H#define HANDLERS_H
#include "ecewo.h"
void handle_login(Req *req, Res *res);void handle_logout(Req *req, Res *res); // We added now
#endif
And also add to entry point:
#include "server.h"#include "handlers/handlers.h"#include "session.h"
int main(){ init_router(); init_sessions();
post("/login", handle_login); get("/logout", handle_logout); // We added it now
ecewo(3000);
reset_sessions(); reset_router();
return 0;}
Now let’s send a request to http://localhost:3000/logout
after login. Cookie
header will be deleted and we’ll see that response:
Logged out
If we send one more request, we’ll see:
You have to login first
WHAT’S THE DIFFERENCE BETWEEN
get_cookie()
andget_session()
?
get_session()
is runningget_cookie()
under the hood, but it’s specialized to extract thesession_id
from theCookie
header. While you need to manually free the memory returned byget_cookie()
, you don’t need to do that withget_session()
— it handles memory management internally.
Getting Session Data
Section titled “Getting Session Data”We added 3 data to the session in the Login
handler: name
, username
and theme
. Let’s write another function that sends the session data:
#ifndef HANDLERS_H#define HANDLERS_H
#include "ecewo.h"
void handle_login(Req *req, Res *res);void handle_logout(Req *req, Res *res);void handle_session_data(Req *req, Res *res); // We added now
#endif
#include "handlers.h"#include "cJSON.h"#include "session.h"
void handle_session_data(Req *req, Res *res){ Session *user_session = get_session(&req->headers);
if (!user_session) { send_text(401, "Error: Authentication required"); return; }
/* Parse the JSON string stored in session->data */ cJSON *session_data = cJSON_Parse(user_session->data); if (!session_data) { /* If parsing fails, return an error */ send_text(500, "Error: Failed to parse session data"); return; }
/* Serialize back to a compact JSON string */ char *session_str = cJSON_PrintUnformatted(session_data); if (!session_str) { cJSON_Delete(session_data); send_text(500, "Error: Failed to serialize session data"); return; }
/* Send the session JSON back to the client */ send_json(200, session_str);
/* Clean up */ free(session_str); cJSON_Delete(session_data);}
#include "server.h"#include "handlers/handlers.h"#include "session.h"
int main(){ init_router(); init_sessions();
get("/session", handle_session_data); // We added it now post("/login", handle_login); post("/logout", handle_logout);
ecewo(3000);
reset_sessions(); reset_router();
return 0;}
First, we need to login. Rebuild the program and send a POST
request to the http://localhost:3000/login
and get the session.
After that, send another request to the http://localhost:3000/session
address to see the session data.
The output will this:
{ "name": "John", "username": "johndoe", "theme": "dark"}
Here are the session data, which we have added while the user is logging in.
If you don’t want the whole session data, but just one or two, you can do it as well:
void handle_session_data(Req *req, Res *res){ Session *user_session = get_session(&req->headers);
if (!user_session) { send_text(401, "Error: Authentication required"); return; }
/* Parse the JSON string stored in session->data */ cJSON *session_data = cJSON_Parse(user_session->data); if (!session_data) { /* If parsing fails, return an error */ send_text(500, "Error: Failed to parse session data"); return; }
cJSON *name = cJSON_GetObjectItem(session_data, "name");
cJSON *response = cJSON_CreateObject();
if (name && name->valuestring) { cJSON_AddStringToObject(response, "name", name->valuestring); } else { cJSON_AddStringToObject(response, "name", "Unknown"); }
char *json_str = cJSON_PrintUnformatted(response); if (!json_str) { cJSON_Delete(session_data); send_text(500, "Error: Failed to serialize session data"); return; }
/* Send the session JSON back to the client */ send_json(200, json_str);
/* Clean up */ free(json_str); cJSON_Delete(session_data); cJSON_Delete(response);}
The output will be:
{ "name": "John"}
Protected Routes
Section titled “Protected Routes”Let’s say that we want some pages to be available for authenticated users only. In this situation, we can use get_session()
function to check if the user has a session.
void handle_protected(Req *req, Res *res);
void handle_protected(Req *req, Res *res){ // Check if the user has session Session *sess = get_session(&req->headers);
// If the user hasn't, return an error with 401 status code if (!sess) { send_text(401, "You must be logged in."); return; }
// If has, let the user in send_text(200, "Welcome to the protected area!");}
get("/protected", handle_protected);
Let’s send a request to http://localhost:3000/protected
. If we authenticated, we’ll see:
Welcome to the protected area!
If we did not, we’ll see:
You must be logged in.
Well, some routes should be for the user’s himself only such as Edit Profile page. In that situation, we need to think deeper.
For this example, we’ll define a route with slug
and we’ll check first if the username in session data and slug is the same.
If they are, then we’ll run a sql query and send a response.
int main(){ // <-- Some other codes -->
get("/edit/:slug", edit_profile);
// <-- Some other codes -->}
void edit_profile(Req *req, Res *res);
// Example user info (it must be saved in a database)const char *STATIC_NAME = "John Doe";const char *STATIC_USERNAME = "johndoe";const char *STATIC_PASSWORD = "123123123";const int STATIC_USER_ID = 1;
void edit_profile(Req *req, Res *res){ Session *sess = get_session(&req->headers); if (!sess) { send_text(401, "Authentication required"); return; }
cJSON *session_data = cJSON_Parse(sess->data); if (!session_data) { send_text(500, "Invalid session data"); return; }
const cJSON *username = cJSON_GetObjectItem(session_data, "username"); const char *slug = get_params("slug");
if (!slug || strcmp(slug, username->valuestring) != 0) { cJSON_Delete(session_data); send_text(403, "Unauthorized: Slug does not match session username"); return; }
if (strcmp(slug, STATIC_USERNAME) == 0) { cJSON *user_json = cJSON_CreateObject(); cJSON_AddNumberToObject(user_json, "id", STATIC_USER_ID); cJSON_AddStringToObject(user_json, "name", STATIC_NAME);
char *json_string = cJSON_PrintUnformatted(user_json); send_json(200, json_string);
cJSON_Delete(user_json); free(json_string); } else { send_text(404, "User not found"); }
cJSON_Delete(session_data);}
Let’s send 3 different request to the http://localhost:3000/edit/johndoe
route.
If we try without any authorization, we’ll get that response:
Authentication required
If we try to reach that page as someone who is not johndoe, we’ll receive:
Unauthorized: Slug does not match session username
When we logged in as johndoe and send a request again, here is what we will get:
{ "id": 1, "name": "John"}
NOTE 1
It’s not safe to insert the password to the database without encryption. You should use a library to encrypt the user password before inserting.
NOTE 2
In these examples, session is stored in memory, but you can store them in the database if you prefer.
If you store them in the memory, you will use
free_session()
anddelete_session()
API for rare operations like logout. Ecewo will free the expired sessions when a new session is created.But if you prefer storing the sessions in a database, you may free the session from memory right after you create and insert it into the database.
We can use l8w8jwt for authentication with JWT.
There are basic examples in the GitHub page of l8w8jwt
. We’ll do the same examples in our Ecewo project:
#ifndef HANDLERS_H#define HANDLERS_H
#include "ecewo.h"
void encoding_handler(Req *req, Res *res);void decoding_handler(Req *req, Res *res);
#endif
#include "server.h"#include "handlers.h"
int main(){ init_router();
get("/decode", decoding_handler); get("/encode", encoding_handler);
ecewo(3000); reset_router(); return 0;}
#include "handlers.h"#include "l8w8jwt/encode.h"#include "l8w8jwt/decode.h"
static const char KEY[] = "YoUR sUpEr S3krEt 1337 HMAC kEy HeRE";static const char JWT[] = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1ODA5MzczMjksImV4cCI6MTU4MDkzNzkyOSwic3ViIjoiR29yZG9uIEZyZWVtYW4iLCJpc3MiOiJCbGFjayBNZXNhIiwiYXVkIjoiQWRtaW5pc3RyYXRvciJ9.7oNEgWxzs4nCtxOgiyTofP2bxZtL8dS7hgGXRPPDmwQWN1pjcwntsyK4Y5Cr9035Ro6Q16WOLiVAbj7k7TeCDA";
void encoding_handler(Req *req, Res *res){ char *jwt; size_t jwt_length;
struct l8w8jwt_encoding_params params; l8w8jwt_encoding_params_init(¶ms);
params.alg = L8W8JWT_ALG_HS512;
params.sub = "Gordon Freeman"; params.iss = "Black Mesa"; params.aud = "Administrator";
params.iat = l8w8jwt_time(NULL); params.exp = l8w8jwt_time(NULL) + 600; /* Set to expire after 10 minutes (600 seconds). */
params.secret_key = (unsigned char *)"YoUR sUpEr S3krEt 1337 HMAC kEy HeRE"; params.secret_key_length = strlen(params.secret_key);
params.out = &jwt; params.out_length = &jwt_length;
int r = l8w8jwt_encode(¶ms);
printf("\n l8w8jwt example HS512 token: %s \n", r == L8W8JWT_SUCCESS ? jwt : " (encoding failure) ");
/* Always free the output jwt string! */ l8w8jwt_free(jwt);
send_text(200, "Success!");}
void decoding_handler(Req *req, Res *res){ struct l8w8jwt_decoding_params params; l8w8jwt_decoding_params_init(¶ms);
params.alg = L8W8JWT_ALG_HS512;
params.jwt = (char *)JWT; params.jwt_length = strlen(JWT);
params.verification_key = (unsigned char *)KEY; params.verification_key_length = strlen(KEY);
/* * Not providing params.validate_iss_length makes it use strlen() * Only do this when using properly NUL-terminated C-strings! */ params.validate_iss = "Black Mesa"; params.validate_sub = "Gordon Freeman";
/* Expiration validation set to false here only because the above example token is already expired! */ params.validate_exp = 0; params.exp_tolerance_seconds = 60;
params.validate_iat = 1; params.iat_tolerance_seconds = 60;
enum l8w8jwt_validation_result validation_result;
int decode_result = l8w8jwt_decode(¶ms, &validation_result, NULL, NULL);
if (decode_result == L8W8JWT_SUCCESS && validation_result == L8W8JWT_VALID) { printf("\n Example HS512 token validation successful! \n"); } else { printf("\n Example HS512 token validation failed! \n"); }
/* * decode_result describes whether decoding/parsing the token succeeded or failed; * the output l8w8jwt_validation_result variable contains actual information about * JWT signature verification status and claims validation (e.g. expiration check). * * If you need the claims, pass an (ideally stack pre-allocated) array of struct l8w8jwt_claim * instead of NULL,NULL into the corresponding l8w8jwt_decode() function parameters. * If that array is heap-allocated, remember to free it yourself! */
send_text(200, "Success!");}
Rebuild the project and send two different requests. First one is http://localhost:3000/encode
. We’ll receive a Success!
response and there will be a token in the terminal:
l8w8jwt example HS512 token: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NDg4NjEyMjgsImV4cCI6MTc0ODg2MTgyOCwic3ViIjoiR29yZG9uIEZyZWVtYW4iLCJpc3MiOiJCbGFjayBNZXNhIiwiYXVkIjoiQWRtaW5pc3RyYXRvciJ9.nVuPVFtW3DqCI-XQDvBWG_OfvuDH6Tt_MR7f72Dpq8sztkTWs6pO6OJDh_3Eeh5xbVLqbTxiXILPCfo6NkgCwA
Now let’s send a request to http://localhost:3000/decode
for decoding. We’ll receive a Success!
response again and see that in the terminal:
Example HS512 token validation successful!