If you like this project then give it a Github star! π€©β
Authentication API that implements a refresh token rotation scheme (based on JWT tokens) and token reuse detection using Node.js, Express and Typescript
=================
- About
- Table of Contents
- Project Description
- Prerequisites
- Installation
- How to use
- Technologies
- TODO list
- License
- Author
This project showcases an authentication API built with Node.js and Express.js, featuring the following major functionalities:
- User registration
- Sign in/Sign out
- Refresh access tokens
- Retrieve and update user profiles
- Admin features (create new accounts, retrieve complete user lists, retrieve and update individual users, delete users)
- Mock protected routes
- Retrieve sample data with pagination
- The data endpoints provide fake Airbnb data for consumption by sample applications. The original database was retrieved from MongoDB Atlas (for more information, visit: https://www.mongodb.com/docs/atlas/sample-data/sample-airbnb/).
The project was developed using the following technologies and tools: Node.js, Express, Husky, lint-staged, eslint, prettier and commitlint with conventional commits.
To store user data, sign-in tokens, and serve dummy data (i.e., the Airbnb sample data), the API utilizes a MongoDB server. Additionally, a Redis server instance is employed to block access to signed-out access tokens, thereby preventing unauthorized access by unexpired access tokens.
You can try out this API by running it in your local environment, within a Docker container, or by accessing it directly from the web. The installation instructions vary depending on the target environment.
Refresh token rotation is a secure technique used to manage authentication tokens that keep user sessions active. When authenticating with user/password credentials, the API generates two types of tokens: the refresh token and the access token. These tokens can be opaque strings or use a specific technology like JWT (JSON Web Token).
The refresh token should be stored as a browser cookie with the following flags: (a) Secure
- it can only be transferred over HTTPS connections; and (b) HttpOnly
- it cannot be accessed using JavaScript code. On the other hand, the access token is returned in the API response body and should be stored in the browser memory (preferably avoiding the use of the localStorage due to security concerns). The access token should have a short lifespan for increased security, meaning it should expire within a short period.
To access restricted API endpoints, all requests need to include the access token in the Authorization Header. The API validates the access token, granting access if it's valid. If the access token has expired, the client needs to refresh both the access token and the refresh token. If the old tokens are still valid, they should be temporarily stored in MongoDB and Redis to prevent unauthorized access attempts.
If the current refresh token is not valid during the token refresh process, it indicates that the user needs to perform a new authentication. The following image illustrates the communication flow between the client and server:
(image credits to Supertokens)
Before starting, you need Node.js, Yarn and Git. Docker also needs to be installed and configured, since this application depends on MongoDB and Redis servers.
# Clone this repository
$ git clone https://github.com/rafaelfl/express-typescript-auth
# Enter in the project folder in terminal/cmd
$ cd express-typescript-auth
After installing the tools and the source code, you need to configure the project using a .env
file. Using that file one can customize the API secrets and configuration. An example file .env.example
is provided and can be copied and updated. The content and configuration variables available in the .env
file are:
DATABASE_URL="<MONGODB DATABASE URL>"
DATABASE_USER="<MONGODB ACCESS USERNAME>"
DATABASE_PASSWORD="<MONGODB ACCESS PASSWORD>"
PORT=<PORT NUMBER (3000 IS THE DEFAULT)>
SALT=<PASSWORD HASH GENERATION SALT NUMBER (10 IS THE DEFAULT)>
REFRESH_TOKEN_PRIVATE_KEY="<SECRET KEY USED TO ENCRYPT THE JWT REFRESH TOKEN>"
ACCESS_TOKEN_PRIVATE_KEY="<SECRET KEY USED TO ENCRYPT THE JWT ACCESS TOKEN>"
REFRESH_TOKEN_EXPIRATION="<EXPIRATION TIME FOR THE REFRESH TOKEN>"
ACCESS_TOKEN_EXPIRATION="<EXPIRATION TIME FOR THE ACCESS TOKEN>"
COOKIE_DOMAIN="<DOMAIN TO BE USED WHEN CREATING THE REFRESH TOKEN COOKIE>"
REDIS_URL="<REDIS DATABASE URL>"
The REFRESH_TOKEN_EXPIRATION
and ACCESS_TOKEN_EXPIRATION
can be expressed as a time formatted string with a value and a time unit, such as: "5h", "40m", "320". They accept "h" for hours, "m" for minutes and any other value is considered as seconds (important: the "s" for seconds is NOT supported - any other numerical value is considered as seconds by default).
After configuring the .env
file, you can start installing the dependencies, building and running the project.
To run the project locally, you need to make sure you have MongoDB and Redis servers running locally. The necessary URLs and credentials should be configured in the .env
file. If you don't have the servers running locally, you can utilize the servers available in the Docker environment (follow the instructions available in the Section Running the project in a Docker container), or you can connect to remote instances of MongoDB and Redis servers (such as, MongoDB Atlas and Redis Cloud).
Before running the API service, the MongoDB database needs to be populated with initial data. To accomplish this, you must install the MongoDB database tools
on your system (you can follow the installation instructions available in this link). After the installation, the mongorestore
command should be available for use in the current path.
# Verifying the mongorestore version
$ mongorestore --version
mongorestore version: 100.7.3
git version: ad89a1c6dbe283012012cf0e5f4cb7fb1fcdf75d
Go version: go1.19.9
os: darwin
arch: arm64
compiler: gc
With mongorestore
available in the current path, you can execute the following command to seed the database:
# Seeding the database with initial data
$ yarn db:seed
IMPORTANT: If you are running the API service in your local environment, ensure that your MongoDB and Redis URLs are configured to point to your localhost
(127.0.0.1) or other appropriate hosts. The default settings in the .env.example
file specify the hostnames mongo
("mongodb://mongo:27017") and redis
("redis://redis:6379"), which are accessible only within the Docker runtime environment.
Once you have installed the necessary tools and obtained the source code, the next step is to install the required dependencies, to build and run the project.
# Install dependencies
$ yarn install
# Build project
$ yarn build
# Run the project
$ yarn start
In case you need to run the code in development mode, you can use the following command:
# Run the API in development mode
$ yarn dev
In the development mode, any change in the API source code is automatically reloaded and applied.
If you are using the PORT=3000
environment variable (the default), the application will be available on http://localhost:3000
.
Some other interesting commands:
yarn clean
- clean the build filesyarn dev
- run the service in watch mode (withnodemon
)yarn build
- build the page for deployingyarn start-ts
- run the application directly transpiling from the Typescript files (withts-node
)yarn lint
- run the linter to identify some problems in codeyarn lint-fix
- run the linter to identify and fix problems in codeyarn prettier
- run the prettier formatteryarn test
- run the integration testsyarn test-coverage
- run the tests coverage
To run the project directly in a Docker container, you can build and run the image using the docker-compose
command.
# Build and run the current Docker image
$ docker-compose up -d
Running the command in the project directory initiates the installation of dependencies, builds the image, and runs the container. Following this process, the service should be operational, utilizing the configuration specified in the .env
file.
For the API service to have access to MongoDB and Redis instances, the .env
file used during container construction should use the hostnames mongo
and redis
(since they are used to provide access to other containers). If you wish to seed the MongoDB instance running in the Docker container, directly from your system, update your local .env
file to utilize localhost
(127.0.0.1) servers before running yarn db:seed
.
The applications will be available in the following ports:
- Authentication API -
http://localhost:3000
- MongoDB -
mongodb://localhost:27017/authusers
- Redis -
redis://localhost:6379
- Mongo Express (used to manage MongoDB through a web interface) -
http://localhost:8081/db/authusers/
IMPORTANT: To utilize the Dockerized MongoDB and Redis servers, while running the API service locally, follow these steps:
- Start the containers containing the MongoDB and Redis servers using the appropriate command for your Docker setup (i.e.,
docker-compose up -d
); - Stop the API service, executing the following command:
# Stop the API service container
$ docker stop server
By following these instructions, you can have the MongoDB and Redis servers running in Docker and available to be used by the local API service instance.
This API was deployed on Render and it is available for testing through the following URL:
IMPORTANT: When accessing the application for the first time using the previous URL, it may take a while due to its startup time. Free Render instances are automatically shut down after 15 minutes of inactivity.
IMPORTANT 2: The project uses a dynamic origin CORS configuration. It verifies if the origin domain exists in a list of allowed domains during the browser CORS check. The allowed domains are: "http://localhost:3000", "http://localhost:3001", "https://localhost:3000" and "https://login.rafaelf.dev:3000". As can be seen, some domains are HTTP domains. They can be used for testing, but since the refresh token has the "Secure" flag, no token refreshing can be performed in HTTP domains (so, when the access token expires, a new login should be performed).
Once the service is up and running, you have the flexibility to access its endpoints using any REST client of your choice. The API documentation has been prepared using Swagger, and you can access it through the following link:
Authentication API Documentation
If you have seeded the database or are utilizing the online API, the following demo users can be used to log in to the API:
Password Access [email protected]
123456
User Access [email protected]
admin
Admin Access
A Postman collection is available and can be accessed through the following button:
To access restricted API endpoints, you will need a (short-lived) access token. To obtain your access token, send a request to /login
along with your username
and password
credentials. The API will return both the access token and a refresh token, which can be used by the client to request new short-lived access tokens when needed.
Sample Response:
POST http://localhost:3000/login
HTTP/1.1
Accept: application/json
{
"email": "[email protected]",
"password": "123456"
}
HTTP/1.1 200 OK
Content-Type: application/json
Headers:
Set-Cookie refreshToken=...; Max-Age=86400; Domain=localhost; Path=/; Expires=Tue, 18 Jul 2023 02:16:54 GMT; HttpOnly; Secure; SameSite=None
{
"success": true,
"data": {
"token": "..."
}
}
- Node.js
- Express.js
- Express Validator
- TypeScript
- Yarn
- MongoDB
- Redis
- jsonwebtoken
- Mongoose
- Passport
- Morgan
- Winston
- Swagger
- Jest
- Supertest
- Mockingoose
- Husky
- eslint
- lint-staged
- prettier
- commitlint
- Forgot password
- Change password
This project is open-sourced software licensed under the MIT license.
Rafael Fernandes Lopes
Developed with π by Rafael Fernandes Lopes