Authenticate users and secure access to backend data
- Install dependencies
bcrypt,passport,passport-local,passport-jwtandjsonwebtoken
Authentication is used to know which user performs an action. The backend can deny access to resources for not authenticated users. The authentication can also be used to scope the user's access.
There are multiple techniques for limiting users permissions, we will see some on another workshop. The goal for today is to offer registration and authentication capabilities.
Anyone can register an account on our backend by giving a username and a password.
We NEVER store plaintext passwords !
Once a registration request is received, the password is hashed with bcrypt, and the user is stored with this
password hash in database.
Use a salt when hashing password, to protect your data against
Rainbow Table attacks (table containing a list of hash with their
non-hashed value)
A registered user can "Log-In" to our backend by providing its username and password.
Once a login request is received, the backend will hash the password and compare it with the hash stored on the user. If hashes match, the backend will deliver a JSON Web Token (JWT), a proof of authentication containing the user's ID.
For any protected requests, the user must give its JWT (issued by backend on Login). A middleware will check JWT authenticity and accept or deny the request.
The JWT is given in request's headers: Authorization: Bearer eyJh[...]
Client (User) -> Makes a POST /locations request with its JWT -> JWT Middleware checks JWT:
- Case 1: Valid JWT -> Proceed to location creation
- Case 2: Invalid JWT (expired, unsigned, wrong signature...) -> 403 Error
You can play with JWT at JWT.io
A middleware is a function called before or after a route handler. Basically, you can have an existing route:
router.get('/locations', (req, res) => res.send(200).body({ locations: [] }))Now you want to protect it from unauthenticated users. Middlewares can help !
router.get('/locations', checkUser, (req, res) => res.send(200).body({ locations: [] }))Or, to apply it to multiple routes:
router.use(checkUser)
router.get('/locations', (req, res) => res.send(200).body({ locations: [] }))The wonderful thing is that middlewares have access to req and res objects, meaning they can throw errors before the
actual route handler, or add data to request!
This is where Passport middlewares shines:
router.use(checkUser) // Passport Middleware. If User is authenticated, it is added to req
router.get('/locations', (req, res) => res.send(200).body({ locations: [], user: req.user }))- Create a new resource:
Users- Create a new
usersfolder containing a user model, service and controller- A
userhas a username and a password
- A
- Register the
users.controller.jsin Express router (index.js). The controller must offer the following routes:- Register POST
/users/register - Login POST
/users/login - Get self GET
/users/me(implement it later, using JWT Auth) - Update self PUT/PATCH
/users/me(implement it later, using JWT Auth) - Delete self DELETE
/users/me(implement it later, using JWT Auth) - Get all GET
/users(remember to not return users passwords on this route)
- Register POST
- Create a new
- Implement the User Registration route
- Ensure username is unique
- Hash the password with bcrypt and save it in Mongo with the username
- Create a folder to store Passport Strategies (local and JWT strategies)
- Implement the User Login route with
passport-local- Use a
passport-localstrategy for this, as a middleware before the route handler:- Find the user by its username
- Hash the password with bcrypt and compare it with found user in Mongo (403 if not matching, Passport handles it)
- Route handler: Sign a JWT (
jsonwebtokenpackage) containing the user's ID assub. Use a JWT Secret from your.env file
- Use a
- Implement a JWT Middleware with a JWT Passport Strategy (
passport-jwt)- Get User from Mongo with the JWT
subdata
- Get User from Mongo with the JWT
- Use the JWT middleware to protect all
/locationsroutes - Use the JWT middleware to implement and protect the
/users/meCRUD routes