Skip to content

Inspired by an article, here's an implementation on sessions with TypeScript, Fastify & Drizzle ORM.

License

Notifications You must be signed in to change notification settings

kauefraga/learn-sessions

Repository files navigation

Learn sessions

Bluesky: @kauefraga.dev Dev.to: kauefraga Last commit

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.

Testing the implementation manually with an HTTP client

🔥 Features

  • Simple API specification
  • Cookie HTTP only, signed and max age set
  • Session validator
  • Package by layer
  • "Keep me signed in"
  • CSRF mitigation (SameSite property)

🔑 Concepts

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:

📜 API specification

Base URL: http://localhost:3333

The REST API has the following use cases:

Create user

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

Authenticate user

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

Log out user

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

Fetch users

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

Forget password

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

Reset password

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

🛠 How to run

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-sessions

Install the dependencies of the project

pnpm install

Start the database container

docker compose up -d

Define secrets in .env

cp .env.example .env

Run database migrations

pnpm drizzle-kit migrate

Run the server

pnpm dev

💭 Considerations

About 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

📝 License

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 ⭐

About

Inspired by an article, here's an implementation on sessions with TypeScript, Fastify & Drizzle ORM.

Topics

Resources

License

Stars

Watchers

Forks