· 9 years ago · May 24, 2017, 02:54 PM
1# Express Auth Step-by-Step
2
3## Setup
4
5Install the necessary dependencies.
6- `passport`
7- `passport-local`
8- `bcryptjs`
9- `cookie-parser`
10- `dotenv`
11- `express-session`
12
13## Create the User table.
14
15In a new migration file in `db/migrations`:
16
17```sql
18# filename migration_[current-date].sql
19\connect [your_database_here]
20
21CREATE TABLE IF NOT EXISTS users (
22 id BIGSERIAL PRIMARY KEY,
23 username VARCHAR(255) UNIQUE NOT NULL,
24 first_name VARCHAR(255),
25 last_name VARCHAR(255),
26 email VARCHAR(255),
27 password TEXT NOT NULL
28);
29```
30
31Then, run the migration: `psql -f migration_[date].sql`
32
33## Set up your `.env` style and add it to your `.gitignore`.
34
35In the root directory of your app, create a file `.env`. **IMMEDIATELY ADD IT TO YOUR GITIGNORE BEFORE YOU MAKE ANY OTHER CHANGES!!!!!!!**
36
37In `.env`, write this line:
38
39```
40SECRET_KEY=lsdjflskjdflkjsdflsdjfoiwerjlksdjflsd
41```
42
43... except use a different secret key.
44
45```bash
46# You can generate a secret key using Python on the command line like so:
47$ python
48>>> import os
49>>> os.urandom(24)
50"\x02\xf3\xf7r\t\x9f\xee\xbbu\xb1\xe1\x90\xfe'\xab\xa6L6\xdd\x8d[\xccO\xfe"
51# put this in your .env!
52```
53
54## Add the new dependencies to your `app.js`.
55
56**Remember, don't copy and paste!!!!**
57
58**STEP ONE**: Importing the new dependencies.
59
60```js
61// in app.js under requiring method override
62const session = require('express-session');
63const passport = require('passport');
64
65// this will get our envorinment variables from the .env file
66require('dotenv').config();
67```
68
69**STEP TWO**: Setting the app up to use our new middlewares!
70
71```js
72// in app.js under `app.use(methodOverride('_method'))`
73app.use(session({
74 secret: process.env.SECRET_KEY,
75 resave: false,
76 saveUninitialized: true,
77}));
78app.use(passport.initialize());
79app.use(passport.session());
80```
81
82## Add the views for logging in and registering.
83
84Refer to [these files](./views/auth) to see how these should look.
85
86## Create a User model.
87
88**Step One**: In `models`, create a new file `user.js`.
89
90It should look like this:
91
92```js
93const db = require('../db/config');
94
95const User = {};
96
97User.findByUserName = userName => {
98 return db.oneOrNone('SELECT * FROM users WHERE username = $1', [userName]);
99};
100
101User.create = user => {
102 return db.one(
103 `
104 INSERT INTO users
105 (username, first_name, last_name, email, password)
106 VALUES ($1, $2, $3, $4, $5) RETURNING *
107 `,
108 [user.username, user.first_name, user.last_name, user.email, user.password]
109 )
110};
111
112module.exports = User;
113```
114
115## Setting up Passport.
116
117### auth directory
118
119Create a 'services' directory in the root of your app, and an `auth` directory inside that.
120
121Add the following files to the auth directory: `auth-helpers.js`, `local.js`, and `passport.js`.
122
123### auth-helpers.js
124
125This file will contain various helper functions that we use throughout our app. For now we are just going to add a function that will use bcrypt to compare passwords. Add the following code:
126
127```javascript
128const bcrypt = require('bcryptjs');
129const User = require('../../models/user');
130
131
132function comparePass(userPassword, databasePassword) {
133 return bcrypt.compareSync(userPassword, databasePassword);
134}
135```
136
137### passport.js
138
139Add the following code:
140
141```javascript
142const passport = require('passport');
143const User = require('../../models/user');
144
145module.exports = () => {
146 passport.serializeUser((user, done) => {
147 done(null, user.username);
148 });
149
150 passport.deserializeUser((username, done) => {
151 User.findByUserName(username)
152 .then(user => {
153 done(null, user);
154 })
155 .catch(err => {
156 done(err, null);
157 });
158 });
159};
160```
161
162### local.js
163
164Add the following code:
165
166```javascript
167const passport = require('passport');
168const LocalStrategy = require('passport-local').Strategy;
169
170const init = require('./passport');
171const User = require('../../models/user');
172const authHelpers = require('./auth-helpers');
173
174const options = {};
175
176init();
177
178passport.use(
179 new LocalStrategy(options, (username, password, done) => {
180 User.findByUserName(username)
181 .then(user => {
182 if (!user) {
183 return done(null, false);
184 }
185 if (!authHelpers.comparePass(password, user.password)) {
186 return done(null, false);
187 } else {
188 return done(null, user);
189 }
190 })
191 .catch(err => {
192 console.log(err);
193 return done(err);
194 });
195 })
196);
197
198module.exports = passport;
199```
200
201
202
203## Setting up our register, login, logout, & user routes
204
205### GET /auth/register
206
207Now lets add the ability to register users. To do that we first need a registration form and a register route. In the routes directory, add `authRoutes.js`. Add the following code:
208
209```javascript
210const express = require('express');
211const authRouter = express.Router();
212const passport = require('../services/auth/local');
213const authHelpers = require('../services/auth/auth-helpers');
214
215
216authRouter.get('/login', authHelpers.loginRedirect, (req, res) => {
217 res.render('auth/login');
218});
219
220authRouter.get('/register', authHelpers.loginRedirect, (req, res) => {
221 res.render('auth/register');
222});
223```
224
225Add this to `services/auth/authHelpers`:
226
227```javascript
228function loginRedirect(req, res, next) {
229 if (req.user) res.redirect('/user');
230
231 return next();
232}
233```
234
235
236
237For now it will always `return next()`. Let's add our route to actually register the user!
238
239### POST /auth/register
240
241When the user posts to the `/auth/register` route, the browser will send all the data contained in the form field to our express server. Our route middleware will then create a new user with that data. Add the following code to the `routes/authRoutes.js` file:
242
243```javascript
244authRouter.post('/register', (req, res, next) => {
245 authHelpers.createNewUser(req, res)
246 .then((user) => {
247 req.login(user, (err) => {
248 if (err) return next(err);
249
250 res.redirect('/user');
251 });
252 })
253 .catch((err) => { res.status(500).json({ status: 'error' }); });
254});
255```
256
257The actual work of creating the user is offloaded to a function in our `auth-helpers` file. Let's add that code to that file:
258
259```js
260function createNewUser(req, res) {
261 const salt = bcrypt.genSaltSync();
262 const hash = bcrypt.hashSync(req.body.password, salt);
263 return User.create({
264 username: req.body.username,
265 first_name: req.body.first_name,
266 last_name: req.body.last_name,
267 email: req.body.email,
268 password: hash,
269 });
270}
271```
272
273Now that we can register users, let's give them the ability to log in.
274
275### POST /auth/login
276
277First we have to provide a page to log in. Add the following route to `routes/authRoutes`:
278
279```javascript
280authRouter.get('/login', authHelpers.loginRedirect, (req, res)=> {
281 res.render('auth/login');
282});
283```
284
285Passport makes this POST route handler pretty easy to write. Add the following code to `routes/authRoutes`:
286
287```javascript
288authRouter.post('/login', passport.authenticate('local', {
289 successRedirect: '/user',
290 failureRedirect: '/auth/login',
291 failureFlash: true
292 })
293);
294```
295
296Passport authenticates the user for us based on the strategy we tell it to, in this case the local strategy. It authenticates according to the function in `auth/local.js`. Refer back to that to see what's going on there.
297
298### GET /logout
299
300Logging out is pretty straightforward. Add the following, again, to `routes/authRoutes`:
301
302```javascript
303authRouter.get('/logout', (req, res) => {
304 req.logout();
305 res.redirect('/');
306});
307
308module.exports = authRouter;
309```
310
311### GET /user
312
313Now that users can log in, we'll give them a user profile page. Let's add the following code to `routes/users`:
314
315```javascript
316const express = require('express');
317const userRoutes = express.Router();
318const authHelpers = require('../services/auth/auth-helpers');
319
320/* GET users listing. */
321
322userRoutes.get('/', authHelpers.loginRequired, (req, res) => {
323 res.json({ user: 'user profile page placeholder', userInfo: req.user });
324});
325
326module.exports = userRoutes;
327```
328
329We have a new auth helper method here. This, rather than redirecting logged in users, will redirect users that aren't logged in. We're protecting this route. Again, the auth helper is middleware. If the user isn't logged in, they get an error, if they are logged in, the helper function calls `next()` where, according to the route, they get redirected to a user profile page that includes their own user data, to be displayed. Add the following code to the `services/auth/auth-helpers` file:
330
331```javascript
332function loginRequired(req, res, next) {
333 if (!req.user) res.redirect('/auth/login');
334
335 return next();
336}
337
338module.exports = {
339 comparePass,
340 loginRedirect,
341 loginRequired,
342 createNewUser
343}
344```