Inspired by the article "Stop using JWT for sessions" (yes, http), here's an implementation on sessions with TypeScript, Fastify & Drizzle ORM.
This repository aims to explain the concepts of sessions and provide a simple implementation, so you have a solid start point.
- Simple API specification
- Cookie HTTP only, signed and max age set
- Session validator
- Package by layer
- "Keep me signed in"
- CSRF mitigation (
SameSiteproperty)
Cookies can be considered kind of a storage, where you put small pieces of data. In fact, cookies were used as a general client-side data storage before the modern storage APIs (session storage and local storage).
The server sends them to the user's web browser and the browser may store, create new ones, modify existing ones, and send them back to the same server with later requests.
The key idea of cookies is remember state information, because the HTTP protocol is stateless by default.
Cookies are used to store user session information, user preferences, tracking data and other data related to the site.
The localStorage API is used to store client-side data (same as cookies) with the differential of having more space and not sending the data in all requests.
Basically, you can persist more user data using the localStorage API than you would with cookies, however it's not send back to the server with later requests, you have to manually retrieve data, parse it and send.
Neither user session id nor JWT tokens are recommended to be stored in localStorage because of the facility to access them with JavaScript. Cookies HTTP-only solve this issue.
Nevertheless, HTTP-only cookies are exposed to a class of vulnerability called "cross-site request forgery". In order to mitigate this problem, you can set the SameSite property and use anti-CSRF tokens.
Be aware that any XSS vulnerability can overcome any CSRF mitigation.
The session or user session should be created when the user create an account or log in your application. This is used to keep user authentication/authorization (and maybe more) information associated with its proper record.
The HTTP protocol is stateless as mentioned before, it doesn't hold any state, so without sessions the user would need to identify itself for each action that triggers a server call (HTTP request). Sessions solve it :)
To understand why not to use JWT tokens to store user session, read the article: "Stop using JWT for sessions".
References:
Base URL: http://localhost:3333
The REST API has the following use cases:
URL: POST /v1/user/create
Request body:
{
"displayName": "string, optional, max 255 characters,",
"name": "string, max 100 characters",
"email": "string, valid e-mail, max 255 characters",
"password": "string",
"keepSignedIn": "boolean, optional (default: false)"
}Response:
{
"id": "a9bd9d92-fb05-4938-956c-98b9228bdea2",
"displayName": null,
"name": "test",
"email": "[email protected]",
"keepSignedIn": false,
"createdAt": "2024-11-25T01:28:35.230Z"
}Status codes:
- 201: successfully created user and session
Cookies:
- sessionId:
signed cookie containing session uuid
URL: POST /v1/user/auth
Request body:
{
"name": "string, max 100 characters, optional*",
"email": "string, valid e-mail, max 255 characters, optional*",
"password": "string",
"keepSignedIn": "boolean, optional (default: false)"
}Must provide either name or email (*).
Response:
{
"id": "5e5f1642-f36b-4e8b-bc06-cfec569610a0",
"userId": "9f8f7148-c321-4f88-a402-70020404e900",
"startedAt": "2024-11-27T00:59:33.707Z"
}Status codes:
- 201: successfully started user session
- 400: session already exists, required field not provided, user does not exist or invalid credentials
Cookies:
- sessionId:
signed cookie containing session uuid
This route deletes the user session and clear the sessionId cookie.
URL: DELETE /v1/user/logout
Status codes:
- 204: successfully deleted user session
- 401: no session
- 500: failed to delete session
URL: GET /v1/users
Authentication: required, sessionId cookie
Response:
[
{
"id": "a9bd9d92-fb05-4938-956c-98b9228bdea2",
"displayName": null,
"name": "test",
"email": "[email protected]",
"createdAt": "2024-11-25T01:28:35.230Z",
"keepSignedIn": true,
"sessionId": null
},
{
"id": "36df8ca1-e04d-4176-b209-388937a74807",
"displayName": "TypeScript is fantastic",
"name": "ts",
"email": "[email protected]",
"createdAt": "2024-11-25T02:31:50.055Z",
"keepSignedIn": false,
"sessionId": "45c7e6a6-a673-40fa-bcd6-d14f70788585"
},
// ...
]Status codes:
- 200: successfully authorized to see other users
- 401: invalid session, unauthorized
URL: POST /v1/user/forget-password
Request body:
{
"email": "string, valid e-mail, max 255 characters,",
}Response:
{
"id": "a9bd9d92-fb05-4938-956c-98b9228bdea2"
}Status codes:
- 200: successfully sent OTP e-mail
- 400: user does not exist or user already attempted to recover the password
- 500: internally failed
URL: POST /v1/user/reset-password
Request body:
{
"id": "string, valid uuid",
"otp": "string, 6 characters long",
"newPassword": "string",
}Response:
{
"id": "5e5f1642-f36b-4e8b-bc06-cfec569610a0",
"userId": "9f8f7148-c321-4f88-a402-70020404e900",
"startedAt": "2024-11-27T00:59:33.707Z"
}Status codes:
- 201: successfully reset user password
- 400: the OTP does not match, user does not attempted to recover the password or the request expired.
Cookies:
- sessionId:
signed cookie containing session uuid
Make sure you have Node, pnpm and Docker installed in your machine.
Clone the project in your machine
git clone https://github.com/kauefraga/learn-sessions.git
cd learn-sessionsInstall the dependencies of the project
pnpm installStart the database container
docker compose up -dDefine secrets in .env
cp .env.example .envRun database migrations
pnpm drizzle-kit migrateRun the server
pnpm devAbout the authentication abstraction, two options:
- Turn it into a middleware/plugin
- Make it a service and use repository instead of direct database connection
About the project:
- Create front end and integrate with the API
- Use package by feature instead of package by layer
This project is licensed under the MIT License - See the LICENSE for more information.
If this repository has helped you, consider giving it a star ⭐
