How it works...

Perhaps the easiest way to discuss our authentication implementation is to analyze it from a user flow perspective.

The first route, the / (index) route conditionally displays a login or logout link, plus the users name if logged in. The views/index.ejs template contains the logic that checks for a truthiness of a user template local and if it doesn't exist presents a login link, pointing to /auth/login. The user local passed to the template in routes/index.js is the value on req.session.user.

We registered express-session middleware on our Express app in index.js, which means every req object has a session object. However, prior to logging in, the user does not have a session, it's simply an empty object where user is undefined (which equates to false in the views/index.js if statement).

When the /auth/login link is clicked a GET request to /auth/login is made from the browser. In the main index.js file we mounted the auth routes at /auth with app.use. So a request to /auth/login matches the /login GET route in routes/auth.js. The route handler renders the views/login.ejs view with fail set to false.

When the HTML form is filled out and submitted, the browser creates a URL encoded string of the inputs according to the input elements names. So in our case, given the user name of dave and password of ncb the browser creates a request body of pw=dave&un=ncb. The browser makes a POST request to the /auth/login route because that's how the HTML form was configured, the action attributed was set to login (which expands to /auth/login based on relative path), and the method attribute was set to post.

The body-parser module is covered under Route Parameters and POST requests in the There's more... section of the first recipe of this chapter, Creating an Express web app.

In short, a POST request has a message body. By default, browser HTML forms send the message body in URL encoded format (the same as we see with the search portion of GET urls, the parameters after the question mark). We use the urlencoded method of the body-parser module to create body parsing middleware converts an incoming POST request to the /login route to an object on req.body.

The POST /auth/login route handler in routes/auth.js handles the login request and will take one of three actions:

  1. If there is already a session, with an associated user object (that is, if req.session.user exists), it will redirect to the / (index) route.
  2. Next we validate the request POST message (req.body). We do a simple conditional check on username and password, but this could be adapted to check a database of usernames and password hashes.
  3. If the POST body is invalid, the /auth/login POST route will respond to the request by rendering the views/loging.ejs template again, but this time with fail set to true. Which will cause the template to render with a Try Again message.
  1. If the POST body is valid we set req.session.user. At this point, a session identifier is created. Then we redirect to the / route. The redirect response will contain a Set-Cookie HTTP header, containing a key and value that holds the session key name (which defaults to connect.sid) and the session identifier.
Session key name
To avoid server fingerprinting, we should make a practice of configuring web servers to set a generic session key name. See the Avoiding fingerprinting subheading of the There's more... section in the Hardening headers in web frameworks recipe in Chapter 8, Dealing with Security.

As the browser dutifully makes a new request for the / route (as per the HTTP redirection), it will pass the string that came through the Set-Cookie header back to the browser under the Cookie header.

The express-session middleware will intercept the request, and recognize the connect.sid portion of the Cookie header, extract the session identifier, and query the session storage for any state that is associated with the identifier. State is then placed onto the req.session object. In our case the user object is added to req.session. The routes/index.js GET handler will pass the req.session.user object into the res.render method, and the views/index.ejs template will enter the first logic branch in the conditional statement checking for truthiness of the user template local.

This time when the views/index.ejs renders, it will include a welcome message to the logged in user (Hi dave!) and a logout link pointing to /auth/logout.

When the /auth/logout link is clicked, the /logout GET route in routes/auths.js is passed the request and response objects. We set req.session to null , which unsets the session (the session data is released from the session store). Then we redirect back to the index route (/).

The browser will continue to cache and send the obsolete session cookie until it expires, however, the session identifier will have no matches in the store, so our server will treat the browser as if it has no session (which it doesn't). Upon logging in again, the browser receives a new session cookie that replaces the old cookie.

The session cookie sent to the browser may or may not be marked with a Secure attribute. This attribute instructs the browser to never send the session cookie back to the server over HTTP - this helps to avoid person in the middle attacks designed to steal session cookies. However, the Secure attribute cannot be set in the first place if an incoming request is made over HTTP (and of course, even if it could, we wouldn't be able to receive the users session cookie back without HTTPS).

In production, we should absolutely use HTTPS and send secure session cookies, in development it's more convenient to use HTTP (which means we can't use secure cookies).

So we assume a production environment that applies SSL encryption to connections at a load balancer or reverse proxy layer, but our web server serves content to the load balancer (or reverse proxy) via HTTP. To make this work we set the Express trust proxy setting to 1. Which means trust exactly one proxy IP to deliver unencrypted content as if it was encrypted when that proxy adds an X-Forwarded-Proto header with the value of HTTPS to the request. When this is the case, express-session will set the Secure attribute on the session cookie, even though it's technically served over HTTP to the proxy layer.

The trust proxy setting can also (preferably) be set to a whitelist array of accepted proxy IP addresses to trust (or for more complex setups, a predicate function that checks the IP for validity). We only do this in production, based on the value of the dev constant (which is set based on the NODE_ENV environment variable). We also use the dev constant to determine whether a session cookie should be secure or not. When we call the session function (which is exported from express-session) we pass an options object, with secure set to the opposite of dev (!dev).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset