{"id":21695595,"url":"https://github.com/kauefraga/learn-sessions","last_synced_at":"2026-04-16T01:33:01.542Z","repository":{"id":264516624,"uuid":"893571228","full_name":"kauefraga/learn-sessions","owner":"kauefraga","description":"Inspired by an article, here's an implementation on sessions with TypeScript, Fastify \u0026 Drizzle ORM.","archived":false,"fork":false,"pushed_at":"2025-01-31T20:44:46.000Z","size":2367,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-25T05:49:53.572Z","etag":null,"topics":["fastify","learning","sessions","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kauefraga.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-11-24T19:26:23.000Z","updated_at":"2025-06-02T12:26:10.000Z","dependencies_parsed_at":"2024-11-24T20:32:11.493Z","dependency_job_id":"6c64e85a-07a7-4056-8983-efb9be12130b","html_url":"https://github.com/kauefraga/learn-sessions","commit_stats":null,"previous_names":["kauefraga/learn-sessions"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kauefraga/learn-sessions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kauefraga%2Flearn-sessions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kauefraga%2Flearn-sessions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kauefraga%2Flearn-sessions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kauefraga%2Flearn-sessions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kauefraga","download_url":"https://codeload.github.com/kauefraga/learn-sessions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kauefraga%2Flearn-sessions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31867710,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","learning","sessions","typescript"],"created_at":"2024-11-25T19:14:53.083Z","updated_at":"2026-04-16T01:33:01.491Z","avatar_url":"https://github.com/kauefraga.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Learn sessions\n\n[![Bluesky: @kauefraga.dev](https://img.shields.io/badge/bluesky-%40kauefraga.dev-blue)](https://bsky.app/profile/kauefraga.dev)\n[![Dev.to: kauefraga](https://img.shields.io/badge/dev.to-kauefraga-242424)](https://dev.to/kauefraga)\n[![Last commit](https://img.shields.io/github/last-commit/kauefraga/learn-sessions/main)](https://github.com/kauefraga/learn-sessions/commits/main)\n\nInspired by the article [\"Stop using JWT for sessions\"](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/) (yes, http), here's an implementation on sessions with TypeScript, Fastify \u0026 Drizzle ORM.\n\nThis repository aims to explain the **concepts of sessions** and provide a **simple implementation**, so you have a solid start point.\n\n\u003cdiv align=\"center\"\u003e\n\n![Testing the implementation manually with an HTTP client](docs/demo.gif)\n\n\u003c/div\u003e\n\n## 🔥 Features\n\n- [x] Simple API specification\n- [x] Cookie HTTP only, signed and max age set\n- [x] Session validator\n- [x] Package by layer\n- [x] \"Keep me signed in\"\n- [x] CSRF mitigation (`SameSite` property)\n\n## 🔑 Concepts\n\n**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).\n\n\u003e 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.\n\nThe key idea of cookies is **remember state information**, because the HTTP protocol is stateless by default.\n\nCookies are used to store user session information, user preferences, tracking data and other data related to the site.\n\nThe **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.\n\nBasically, 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.\n\nNeither 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.\n\nNevertheless, 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.\n\nBe aware that any XSS vulnerability can overcome any CSRF mitigation.\n\nThe **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.\n\nThe 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 :)\n\nTo understand why not to use JWT tokens to store user session, read the article: [\"Stop using JWT for sessions\"](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/).\n\nReferences:\n\n- [Using HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)\n- [Understanding Cookies in Web Browsers](https://www.geeksforgeeks.org/understanding-cookies-in-web-browsers/)\n- [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)\n- [CSRF prevention](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSRF_prevention)\n\n## 📜 API specification\n\nBase URL: `http://localhost:3333`\n\nThe REST API has the following use cases:\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eCreate user\u003c/h3\u003e\u003c/summary\u003e\n\n**URL**: `POST /v1/user/create`\n\n**Request body**:\n\n```json\n{\n  \"displayName\": \"string, optional, max 255 characters,\",\n  \"name\": \"string, max 100 characters\",\n  \"email\": \"string, valid e-mail, max 255 characters\",\n  \"password\": \"string\",\n  \"keepSignedIn\": \"boolean, optional (default: false)\"\n}\n```\n\n**Response**:\n\n```json\n{\n  \"id\": \"a9bd9d92-fb05-4938-956c-98b9228bdea2\",\n  \"displayName\": null,\n  \"name\": \"test\",\n  \"email\": \"test@test.test\",\n  \"keepSignedIn\": false,\n  \"createdAt\": \"2024-11-25T01:28:35.230Z\"\n}\n```\n\n**Status codes**:\n\n- 201: successfully created user and session\n\n**Cookies**:\n\n- sessionId: `signed cookie containing session uuid`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eAuthenticate user\u003c/h3\u003e\u003c/summary\u003e\n\n**URL**: `POST /v1/user/auth`\n\n**Request body**:\n\n```json\n{\n  \"name\": \"string, max 100 characters, optional*\",\n  \"email\": \"string, valid e-mail, max 255 characters, optional*\",\n  \"password\": \"string\",\n  \"keepSignedIn\": \"boolean, optional (default: false)\"\n}\n```\n\nMust provide either name or email (*).\n\n**Response**:\n\n```json\n{\n  \"id\": \"5e5f1642-f36b-4e8b-bc06-cfec569610a0\",\n  \"userId\": \"9f8f7148-c321-4f88-a402-70020404e900\",\n  \"startedAt\": \"2024-11-27T00:59:33.707Z\"\n}\n```\n\n**Status codes**:\n\n- 201: successfully started user session\n- 400: session already exists, required field not provided, user does not exist or invalid credentials\n\n**Cookies**:\n\n- sessionId: `signed cookie containing session uuid`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eLog out user\u003c/h3\u003e\u003c/summary\u003e\n\nThis route deletes the user session and clear the `sessionId` cookie.\n\n**URL**: `DELETE /v1/user/logout`\n\n**Status codes**:\n\n- 204: successfully deleted user session\n- 401: no session\n- 500: failed to delete session\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eFetch users\u003c/h3\u003e\u003c/summary\u003e\n\n**URL**: `GET /v1/users`\n\n**Authentication**: required, `sessionId` cookie\n\n**Response**:\n\n```json\n[\n  {\n    \"id\": \"a9bd9d92-fb05-4938-956c-98b9228bdea2\",\n    \"displayName\": null,\n    \"name\": \"test\",\n    \"email\": \"test@test.test\",\n    \"createdAt\": \"2024-11-25T01:28:35.230Z\",\n    \"keepSignedIn\": true,\n    \"sessionId\": null\n  },\n  {\n    \"id\": \"36df8ca1-e04d-4176-b209-388937a74807\",\n    \"displayName\": \"TypeScript is fantastic\",\n    \"name\": \"ts\",\n    \"email\": \"coreteam@ts.org\",\n    \"createdAt\": \"2024-11-25T02:31:50.055Z\",\n    \"keepSignedIn\": false,\n    \"sessionId\": \"45c7e6a6-a673-40fa-bcd6-d14f70788585\"\n  },\n  // ...\n]\n```\n\n**Status codes**:\n\n- 200: successfully authorized to see other users\n- 401: invalid session, unauthorized\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eForget password\u003c/h3\u003e\u003c/summary\u003e\n\n**URL**: `POST /v1/user/forget-password`\n\n**Request body**:\n\n```json\n{\n  \"email\": \"string, valid e-mail, max 255 characters,\",\n}\n```\n\n**Response**:\n\n```json\n{\n  \"id\": \"a9bd9d92-fb05-4938-956c-98b9228bdea2\"\n}\n```\n\n**Status codes**:\n\n- 200: successfully sent OTP e-mail\n- 400: user does not exist or user already attempted to recover the password\n- 500: internally failed\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003eReset password\u003c/h3\u003e\u003c/summary\u003e\n\n**URL**: `POST /v1/user/reset-password`\n\n**Request body**:\n\n```json\n{\n  \"id\": \"string, valid uuid\",\n  \"otp\": \"string, 6 characters long\",\n  \"newPassword\": \"string\",\n}\n```\n\n**Response**:\n\n```json\n{\n  \"id\": \"5e5f1642-f36b-4e8b-bc06-cfec569610a0\",\n  \"userId\": \"9f8f7148-c321-4f88-a402-70020404e900\",\n  \"startedAt\": \"2024-11-27T00:59:33.707Z\"\n}\n```\n\n**Status codes**:\n\n- 201: successfully reset user password\n- 400: the OTP does not match, user does not attempted to recover the password or the request expired.\n\n**Cookies**:\n\n- sessionId: `signed cookie containing session uuid`\n\n\u003c/details\u003e\n\n## 🛠 How to run\n\nMake sure you have [Node](https://nodejs.org/en), [pnpm](https://pnpm.io/) and [Docker](https://www.docker.com/) installed in your machine.\n\nClone the project in your machine\n\n```sh\ngit clone https://github.com/kauefraga/learn-sessions.git\n\ncd learn-sessions\n```\n\nInstall the dependencies of the project\n\n```sh\npnpm install\n```\n\nStart the database container\n\n```sh\ndocker compose up -d\n```\n\nDefine secrets in `.env`\n\n```sh\ncp .env.example .env\n```\n\nRun database migrations\n\n```sh\npnpm drizzle-kit migrate\n```\n\nRun the server\n\n```sh\npnpm dev\n```\n\n## 💭 Considerations\n\nAbout the authentication abstraction, two options:\n\n- Turn it into a middleware/plugin\n- Make it a service and use repository instead of direct database connection\n\nAbout the project:\n\n- Create front end and integrate with the API\n- Use package by feature instead of package by layer\n\n## 📝 License\n\nThis project is licensed under the MIT License - See the [LICENSE](https://github.com/kauefraga/learn-sessions/blob/main/LICENSE) for more information.\n\n---\n\nIf this repository has helped you, consider giving it a star ⭐\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkauefraga%2Flearn-sessions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkauefraga%2Flearn-sessions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkauefraga%2Flearn-sessions/lists"}