A progressive Node.js framework for building efficient and scalable server-side applications.
Nest framework TypeScript starter repository for an authorization server.
DISCLAIMER: I am not a security expert (took a computer security class in college, but that's about it). I will try to update this repository as I learn more about security best practices. I have linked some of the articles that I found most informative while putting this together at the bottom of this document.
See env/readme.md
for instructions on setting up environment variables. The following environments are required to run the scripts in package.json
(these can be renamed to anything, however, be sure to edit the package.json scripts to use your custom environment names):
dev-test
: All test scripts use this environment, as well as the GitHub workflow.dev
: All start scripts (exceptstart:prod
) use this environment.
This project uses ESLint to catch problems in the code. The settings are defined in .eslintrc.js
.
This project uses Prettier to automatically format code. The settings are defined in .prettierrc
.
This repository is a template repository. You can create a new repository based on this one by clicking Use this template > Create a new repository
at the top of the GitHub repository.
.github/workflows/build-and-test.yml
contains a GitHub Actions workflow for automatically running tests when a pull request is opened. It creates a local postgreSQL database to run the tests. The workflow relies on an actions secret that you must define at Settings > Security > Secrets and variables > Actions > New repository secret
. Name the secret ENV_DEV_TEST
. See the Environments
section above for setting up the properties in this secret. This is basically an env file stored on GitHub. The workflow will grab this secret and convert it into a file named env/.env.dev-test
for use in the testing step.
This is the root module that starts the application. The AppController provides basic information about the app. The following routes are available:
/
: Returns a 'Hello World!' message along with an HTTP status of 200 for verifying the app is running./env
: Returns what environment (e.g., test, prod, etc.) the app is currently running in.
This module provides authentication and authorization services. The following routes are available:
/api/auth/signup
: Registers a new user in the database./api/auth/login
: If user is registered, returns an access token (for accessing protected resources) and a refresh token (for obtaining a new access token after it expires)./api/auth/logout
: Invalidates the user's refresh token. The user will need to login to obtain new tokens./api/auth/refresh
: Refreshes the access and refresh tokens.
The signup, login, and refresh endpoints all return an access token in the response body and a refresh token in an HTTP only cookie. The expirations of each can be set in the env variables (see the Environments
section above). Typically, the access token should have a short life (~15 minutes) while the refresh token is long-lived (7+ days).
The HTTP cookie with the refresh token has the following attributes:
HttpOnly
: Cookie is inaccessible to JavaScript Document.cookie APISecure
: Browser only sends cookie on HTTPS requests (not HTTP)Path=/api/auth/refresh
: Browser only sends cookie if this path is present in the URLSameSite=Strict
: Browser only sends cookie with requests to the cookie's origin site (i.e., this server)
On the client side, upon receiving these tokens, precautions must be taken to ensure they are not compromised. Since the refresh token lives in an HTTP only cookie, it is not accessible to the client or JavaScript in general, so it is protected from cross-site scripting (XSS) attacks. However, it remains vulnerable to cross-site request forgery (XSRF), which can be mitigated by other measures (CORS policy, CSRF tokens, etc.). Based on my research, I have determined that the best place for the client to store the access token is in memory (just a regular variable in the code).
const body = await fetch('http://localhost:8081/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'username',
password: 'password'
})
});
const { accessToken, expiresIn } = body.json();
The alternatives (local and session storage) are not secure. I have attached articles in the resources section at the bottom of this document with more info on the topic.
This module exports a custom LogService that can be used in other modules for application logging. By default, logging is turned off in the dev-test
environment (the environment that all tests are run on). If you want to turn logging off in any other environment, add the property LOGS=false
to the appropriate env file.
This directory (not a module) contains files that are consumed/shared by the other modules.
The Base entity is an abstract entity containing properties that should apply to all entities, like createDate
and updateDate
. If there are other properties that should apply to all entities, add them to this class and have all entities extend from Base.
$ npm install
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
- I referenced tutorials from elvisduru.com and docs.nestjs.com for creating the AuthModule
- Other useful articles: