Node.js By Example: Part 3

John Tucker
codeburst
Published in
5 min readJan 25, 2018

--

Using Passport, we add authentication / authorization to the mix.

This articles is part of the series starting with Node.js By Example: Part 1.

All the examples in this series are available for download. They are Node.js (version ≥ 8.9.3) applications.

Passport

The Passport library provides a generalized infrastructure for authentication / authorization for Express.

Starting from our last, todo, example we are first going to implement a /login endpoint that takes username and password in JSON format; using the passport and passport-local packages.

npm install passport
npm install passport-local

The result of successfully logging in will be a JSON Web Token that can be used to authenticate future API calls.

my-passport/src/index.js

const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

...
const ADMIN = 'admin';
const ADMIN_PASSWORD = 'password';

const app = express();
app.use(bodyParser.json());
app.use((req, res, next) => {
...
});
passport.use(new LocalStrategy((username, password, done) => {
if (username === ADMIN && password === ADMIN_PASSWORD) {
done(null, 'TOKEN');
return;
}
done(null, false);
}));
app.post(
'/login',
passport.authenticate('local', { session: false }),
(req, res) => {
res.send({
token: req.user,
});
},
);
...

Observations:

  • For now, we hard-code in an admin username and password (will refactor later). In more complete solutions, one often looks up the username and password (encrypted) from persistent storage (a database).
  • By initializing Passport using passport.use with a LocalStrategy object, we can later protect the /login endpoint using it (indicated by the local parameter).
  • The done callback (as used here) takes two parameters. The first, if null, indicates there was no error; otherwise indicates an unexpected error (in our case there is no opportunity for an unexpected error). The second is the user object to pass on if authentication is successful; otherwise false.
  • In this simplified example, the user object is simply the string TOKEN. Later we will refactor and create an actual JSON Web Token.
  • We set session to false as we are building a REST interface (no sessions).
  • The return of passport.authenticate(‘local’, { session: false }) is a middleware function that only applies to the /login route. If it successfully authenticates, the third parameter in app.post is called.

In this case, the operation of this middleware is essentially the following:

  1. If the format of the request is bad it will return a 400 status code (has to be a request with Content-Type: application/json header with a JSON body with username and password).
  2. If unauthorized it will return as 401 status code.
  3. If authorized it will add a user property (the user object, the string TOKEN for now, from above) to the request object and pass control to the next parameter in app.post.

To see this in action, we can run the web application:

node src/index.js

and use Postman (or other API tester) to test the /login endpoint.

JWT

The strategy we are going to take is we, once authenticated by username and password, are going to return a JSON Web Token consisting of a server-signed JSON payload including the logged in username. Then, when used on future API requests, the server can validate the signature to determine the authenticated user (by the username).

note: You can also store additional data in the JSON Web Token payload, for example a token expiration date.

Starting from the last example, we first install the jwt-simple package.

npm install jwt-simple

We then simply update index.js.

jwt/src/index.js

...
const LocalStrategy = require('passport-local').Strategy;
const jwt = require('jwt-simple');
...
const ADMIN = 'admin';
const ADMIN_PASSWORD = 'password';
const SECRET = 'mysecret';
...
passport.use(new LocalStrategy((username, password, done) => {
if (username === ADMIN && password === ADMIN_PASSWORD) {
done(null, jwt.encode({ username }, SECRET));
return;
}
done(null, false);
}));
...

With this in place, we run the server and login using Postman to get:

We can use JWT Debugger to deconstruct and validate the token (provided that we supply the secret, mysecret):

The last step in this authentication / authorize exercise is to secure the endpoints; requiring a valid JSON Web Token.

First we install the passport-http-bearer package.

npm install passport-http-bearer

We then use it; much like we did with passport-local.

jwt/src/index.js

...
const LocalStrategy = require('passport-local').Strategy;
const BearerStrategy = require('passport-http-bearer').Strategy;
const jwt = require('jwt-simple');
passport.use(new LocalStrategy((username, password, done) => {
...
}));
passport.use(new BearerStrategy((token, done) => {
try {
const { username } = jwt.decode(token, SECRET);
if (username === ADMIN) {
done(null, username);
return;
}
done(null, false);
} catch (error) {
done(null, false);
}
}));

...
app.get(
'/todos',
passport.authenticate('bearer', { session: false }),
(_, res) => {
Todo.findAll()
...
},
);
app.post(
'/todos',
passport.authenticate('bearer', { session: false }),
(req, res) => {
...
},
);
app.delete(
'/todos/:id',
passport.authenticate('bearer', { session: false }),
(req, res) => {
...
},
);
...

With this in place, we run the server and using Postman we execute GET /todos with the header Authorization: bearer TOKEN (being the JSON Web Token we got from logging in).

Next Steps

In the next article Node.js By Example: Part 4, we will persist the todos in a SQL database.

--

--