https://github.com/jeremyben/json-server-auth
Authentication & Authorization flow for JSON Server
https://github.com/jeremyben/json-server-auth
authentication authorization devtools json-server jwt prototyping
Last synced: 7 months ago
JSON representation
Authentication & Authorization flow for JSON Server
- Host: GitHub
- URL: https://github.com/jeremyben/json-server-auth
- Owner: jeremyben
- License: mit
- Created: 2018-12-16T23:32:59.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2023-07-02T21:03:07.000Z (over 2 years ago)
- Last Synced: 2024-04-25T18:02:49.664Z (over 1 year ago)
- Topics: authentication, authorization, devtools, json-server, jwt, prototyping
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/json-server-auth
- Size: 262 KB
- Stars: 307
- Watchers: 9
- Forks: 69
- Open Issues: 12
-
Metadata Files:
- Readme: README.MD
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 🔐 JSON Server Auth
JWT authentication middleware for **[JSON Server](https://github.com/typicode/json-server)**
Because you also need a fake **authentication & authorization flow** for your prototyping.
## Getting started
Install **both** JSON Server and JSON Server Auth :
```bash
# NPM
npm install -D json-server json-server-auth
# Yarn
yarn add -D json-server json-server-auth
```
Create a `db.json` file with a `users` collection :
```json
{
"users": []
}
```
Start JSON server (with _JSON server Auth_ as middleware) :
```bash
json-server db.json -m ./node_modules/json-server-auth
# with json-server installed globally and json-server-auth installed locally
```
##### 📢 but wait !
As a convenience, **`json-server-auth`** CLI exposes `json-server` bundled with its middlewares :
```bash
json-server-auth db.json
# with json-server-auth installed globally
```
_It exposes and works the same for all [JSON Server flags](https://github.com/typicode/json-server#cli-usage)._
## Authentication flow 🔑
JSON Server Auth adds a simple [JWT based](https://jwt.io/) authentication flow.
### Register 👥
Any of the following routes registers a new user :
- **`POST /register`**
- **`POST /signup`**
- **`POST /users`**
**`email`** and **`password`** are required in the request body :
```http
POST /register
{
"email": "olivier@mail.com",
"password": "bestPassw0rd"
}
```
The password is encrypted by [bcryptjs](https://github.com/dcodeIO/bcrypt.js).
The response contains the JWT access token (expiration time of 1 hour), and the user data (without the password) :
```http
201 Created
{
"accessToken": "xxx.xxx.xxx",
"user": {
"id": 1,
"email": "olivier@mail.com"
}
}
```
###### Other properties
Any other property can be added to the request body without being validated :
```http
POST /register
{
"email": "olivier@mail.com",
"password": "bestPassw0rd",
"firstname": "Olivier",
"lastname": "Monge",
"age": 32
}
```
###### Update
Any update to an existing user (via `PATCH` or `PUT` methods) will go through the same process for `email` and `password`.
### Login 🛂
Any of the following routes logs an existing user in :
- **`POST /login`**
- **`POST /signin`**
**`email`** and **`password`** are required, of course :
```http
POST /login
{
"email": "olivier@mail.com",
"password": "bestPassw0rd"
}
```
The response contains the JWT access token (expiration time of 1 hour), and the user data (without the password) :
```http
200 OK
{
"accessToken": "xxx.xxx.xxx",
"user": {
"id": 1,
"email": "olivier@mail.com",
"firstname": "Olivier",
"lastname": "Monge"
}
}
```
#### JWT payload 📇
The access token has the following claims :
- **`sub` :** the user `id` (as per the [JWT specs](https://tools.ietf.org/html/rfc7519#section-4.1.2)).
- **`email` :** the user `email`.
## Authorization flow 🛡️
JSON Server Auth provides generic guards as **route middlewares**.
To handle common use cases, JSON Server Auth draws inspiration from **Unix filesystem permissions**, especialy the [numeric notation](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation).
- We add **`4`** for **read** permission.
- We add **`2`** for **write** permission.
_Of course CRUD is not a filesystem, so we don't add 1 for execute permission._
Similarly to Unix, we then have three digits to match each user type :
- First digit are the permissions for the **resource owner**.
- Second digit are the permissions for the **logged-in users**.
- Third digit are the permissions for the **public users**.
For example, **`640`** means that only the owner can write the resource, logged-in users can read the resource, and public users cannot access the resource at all.
#### The resource owner 🛀
A user is the owner of a resource if that resource has a **`userId`** property that matches his `id` property. Example:
```js
// The owner of
{ id: 8, text: 'blabla', userId: 1 }
// is
{ id: 1, email: 'olivier@mail.com' }
```
Private guarded routes will use the JWT `sub` claim (which equals the user `id`) to check if the user actually owns the requested resource, by comparing `sub` with the `userId` property.
_Except for the actual `users` collection, where the JWT `sub` claim must match the `id` property._
### Guarded routes 🚥
Guarded routes exist at the root and can restrict access to any resource you put after them :
| Route | Resource permissions |
| :----------: | :--------------------------------------------------------------------------------------------------- |
| **`/664/*`** | User must be logged to _write_ the resource.
Everyone can _read_ the resource. |
| **`/660/*`** | User must be logged to _write_ or _read_ the resource. |
| **`/644/*`** | User must own the resource to _write_ the resource.
Everyone can _read_ the resource. |
| **`/640/*`** | User must own the resource to _write_ the resource.
User must be logged to _read_ the resource. |
| **`/600/*`** | User must own the resource to _write_ or _read_ the resource. |
| **`/444/*`** | No one can _write_ the resource.
Everyone can _read_ the resource. |
| **`/440/*`** | No one can _write_ the resource.
User must be logged to _read_ the resource. |
| **`/400/*`** | No one can _write_ the resource.
User must own the resource to _read_ the resource. |
#### Examples
- Public user (not logged-in) does the following requests :
| _Request_ | _Response_ |
| :-------------------------------------- | :----------------- |
| `GET /664/posts` | `200 OK` |
| `POST /664/posts`
`{text: 'blabla'}` | `401 UNAUTHORIZED` |
- Logged-in user with `id: 1` does the following requests :
| _Request_ | _Response_ |
| :--------------------------------------------------------- | :-------------- |
| `GET /600/users/1`
`Authorization: Bearer xxx.xxx.xxx` | `200 OK` |
| `GET /600/users/23`
`Authorization: Bearer xxx.xxx.xxx` | `403 FORBIDDEN` |
### Setup permissions 💡
Of course, you don't want to directly use guarded routes in your requests.
We can take advantage of [JSON Server custom routes feature](https://github.com/typicode/json-server#add-custom-routes) to setup resource permissions ahead.
Create a `routes.json` file :
```json
{
"/users*": "/600/users$1",
"/messages*": "/640/messages$1"
}
```
Then :
```bash
json-server db.json -m ./node_modules/json-server-auth -r routes.json
# with json-server installed globally and json-server-auth installed locally
```
##### 📢 but wait !
As a convenience, **`json-server-auth`** CLI allows you to define permissions in a more succinct way :
```json
{
"users": 600,
"messages": 640
}
```
Then :
```bash
json-server-auth db.json -r routes.json
# with json-server-auth installed globally
```
You can still add any other _normal_ custom routes :
```json
{
"users": 600,
"messages": 640,
"/posts/:category": "/posts?category=:category"
}
```
## Module usage 🔩
If you go the programmatic way and [use JSON Server as a module](https://github.com/typicode/json-server#module), there is an extra step to properly integrate JSON Server Auth :
⚠️ You must bind the router property `db` to the created app, like the [JSON Server CLI does](https://github.com/typicode/json-server/blob/master/src/cli/run.js#L74), and you must apply the middlewares in a specific order.
```js
const jsonServer = require('json-server')
const auth = require('json-server-auth')
const app = jsonServer.create()
const router = jsonServer.router('db.json')
// /!\ Bind the router db to the app
app.db = router.db
// You must apply the auth middleware before the router
app.use(auth)
app.use(router)
app.listen(3000)
```
#### Permisssions Rewriter
The custom rewriter is accessible via a subproperty :
```js
const auth = require('json-server-auth')
const rules = auth.rewriter({
// Permission rules
users: 600,
messages: 640,
// Other rules
'/posts/:category': '/posts?category=:category',
})
// You must apply the middlewares in the following order
app.use(rules)
app.use(auth)
app.use(router)
```
## TODO 📜
- [ ] Use JSON Server `id` and `foreignKeySuffix` parameters
- [ ] Handle query params in list requests to secure guarded routes more precisely
- [ ] Allow configuration of :
- [ ] Users collection name
- [ ] Minimum password length
- [ ] JWT expiry time
- [ ] JWT property name in response
- [ ] Implement JWT Refresh Token
- [ ] Possibility to disable password encryption ?