An Express/Node.js based server using a custom “authentication” module that is designed to handle user registration and sign in using Basic, Bearer, or OAuth along with a custom “authorization” module that will grant/deny users access to the server based on their role or permissions level.
Our business is expanding! We have a real need to manage a list of users of many types, and control their access to our data accordingly. The system to be built will have the following core features:
admin
can read, create, update, deleteeditor
can read, create, updatewriter
can read, createuser
can readuser
The application will be created with the following overall architecture and methodologies
/signup
to create an account/signin
to login with Basic Auth/oauth
/signin
route only
app.post('/signin', basicAuthentication, (req, res) => { ... })
/oauth
route only
app.get('/oauth', OAuth, (req, res) => { ... })
app.get('/secretstuff', tokenAuthentication, (req, res) => { ... })
app.delete('/secretstuff', tokenAuthRequired, capability('delete'), (req, res) => { ... })
supergoose
to:
We will need to store user information into our system permanently. Use following fields/data types
NOTE: The majority of our work for this server will be done within the
src/auth
folder. The rest of the system should be generic express. Why? It’s our intention to be able to “lift” theauth
folder and “drop” it into any other server (such as our API server) and be able to use authorization to “protect” those CRUD routes. This makes our entire auth system very modular. Think ofindex.js
andserver.js
as nothing more than the basics to get a server started, with 100% of the actual logic living within theauth
module
├── .gitignore
├── .eslintrc.json
├── __tests__
│ ├── auth.router.test.js
│ ├── basic-auth.test.js
│ ├── bearer-auth.test.js
│ ├── 500.test.js
│ ├── model-finder.test.js
│ ├── mongo.js
├── src
│ ├── auth
│ │ ├── router.js
│ │ ├── middleware
│ │ │ ├── basic.js
│ │ │ ├── bearer.js
│ │ │ ├── oauth.js
│ │ ├── models
│ │ │ ├── users-model.js
│ ├── middleware
│ │ ├── 404.js
│ │ ├── 500.js
│ │ ├── model-finder.js
│ ├── server.js
├── index.js
└── package.json
The signup process will require a POST request to the /signup
route.
POST http://yourauthserver.com/signup
{
"username":"awesomecoder",
"password":"youcantguessthis33!",
"fullname":"Awesome Coder",
"email":"awesome@coders.com"
}
Output from the server:
HTTP Headers:
Set-Cookie: auth=ENCRYPTED.JWT.TOKEN; Path=/
token:ENCRYPTED.JWT.TOKEN
Content-Type: application/json
BODY:
{
"token": "ENCRYPTED.JWT.TOKEN",
"user": {
"__v": 0,
"_id": "5ec44dcde458f14f77b3d8c6",
"acl": [],
"password": "$2b$10$Bw5tNNSUACEEHW94CaRItuLsoz/nomUMh4eLTD/T23Ks0mCtUp3Iq",
"role": "user",
"username": "awesomecoder"
}
}
The Basic Authentication processes is initiated by invoking a POST on your /oauth
route with a Basic Authorization header. The Basic Auth Middleware should at this point validate the user account in our database, and return an object with a re-authentication/bearer token and the user object or an error object
POST http://yourauthserver.com/signin
Send Basic Authentication Header
Authorization: Basic encodedpasswordcombo
httpie
from the command line: -a awesomecoder:youcantguessthis33!
Output from the server:
SUCCESSFUL LOGIN:
HTTP Headers:
Set-Cookie: auth=ENCRYPTED.JWT.TOKEN; Path=/
token:ENCRYPTED.JWT.TOKEN
Content-Type: application/json
RESPONSE BODY:
{
"token": "ENCRYPTED.JWT.TOKEN",
"user": {
"acl": [],
"username": "awesomecoder"
}
}
ERROR RESPONSE:
Status Code: 500
{
"error": "Invalid Login"
}
The OAuth processes is initiated in the browser, using a 3rd party authentication/authorization service. It will, once the user has granted permission, invoke a GET on your /oauth
route. The OAuth middleware should at this point complete the handshaking process, create/update a local user account in our database, and return an object with a re-authentication/bearer token and the user object
GET http://yourauthserver.com/oauth
Output from the server should be the same as with **Basic Authentication”:
POST http://yourauthserver.com/signin
Send Basic Authentication Header
Authorization: Basic encodedpasswordcombo
httpie
from the command line: -a awesomecoder:youcantguessthis33!
Output from the server:
HTTP Headers:
Set-Cookie: auth=ENCRYPTED.JWT.TOKEN; Path=/
token:ENCRYPTED.JWT.TOKEN
Content-Type: application/json
BODY:
{
"token": "ENCRYPTED.JWT.TOKEN",
"user": {
"acl": [],
"username": "awesomecoder"
}
}
Re-Authentication can be achieved on any route in the express server, not just the authentication routes.
For example, assume we want to require that a user be logged in, in order to access a route:
From the browser (or agent): GET http://myapi.com/api/v1/food, along with a Bearer Token in the header
On the server, this is handled in a delete route definition and handler
app.get('/api/v1/food', tokenAuthentication, (req, res) => {...});
{
error: "Access Denied"
}
Authorization can be achieved on any route in the express server, not just the authentication routes. Authorization happens after authentication and should match the user’s capabilities with the permissions required.
For example, assume we want to delete record number 12345 from our food database using our REST API
From the browser (or agent): DELETE http://myapi.com/api/v1/food/12345, along with a Bearer Token
On the server, this is handled in a delete route definition and handler
app.delete('/api/v1/food', tokenAuthentication, capabilities('delete'), (req, res) => {...});
{
error: "Access Denied"
}
/signup
route that creates a user/signin
route that attempts to log a user inBasicAuth
middleware that validates the user as a part of the /signin
process