Try   HackMD

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Node.js | Basic Authentication & Sessions

Learning Goals

After this lesson, you will be able to:

  • Understand what authentication is, and how it's related to authorization
  • Understand what sessions and cookies are
  • Understand how authentication, sessions and cookies are all related
  • Implement authentication in your web application

Introduction

Afin d'éviter d'avoir à échanger nos identifiants avec le serveur à chaque requête, nous allons utiliser un mécanisme à base de cookie + session.

In the previous lesson, we learned about authorization and about its importance. In this lesson, we'll learn what is authentication and how it is directly connected to authorization.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

When a user logs into our web app, authentication allows us to know which user (of all users in our system) is visiting the page. While authorization is related to signing up, authentication is related to logging in.

Before we go deeper into the authentication process, we need to learn two new topics that are crucial to it: sessions and cookies.

Cookies

Plutôt que d'échanger ses credentials avec le serveur, à chaque fois que le client souhaite accéder à une ressource privée :

NB : Peu sécure + peu pratique

Nous allons (en tant que client) :

  1. Nous identifier via un formulaire de login :


    👉 Le serveur nous renverra un cookie que nous conserverons dans le client.

  2. puis dans toutes les requêtes suivantes, le cookie sera transmis au serveur afin que celui-ci nous "reconnaisse" :

NB : Ce cookie est la preuve pour le serveur que nous nous sommes bien identifié (par le passé).

Cookies are small files which are stored on a user's computer. They are designed to hold a small amount of specific data from a website and can be accessed either by the web server or the client computer.

Cookies are a convenient way to carry information between sessions, without having to burden a server machine with massive amounts of data storage.

We can write and read cookies with the cookie package, and get them from the server with the cookie-parser installed by default through express-generator.

Once we send a cookie to the client, we can inspect its information with the Chrome Dev Tools. They are located in the Dev Tools' Application tab:

Comment inspecter les cookies stockés dans notre navigateur :

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

A noter que l'ensemble des cookies d'un domain sont envoyés automatiquement lors d'une requête à ce même domaine.

From this tab, you can see the content of the cookies you have in your machine. You can also remove the cookies you don't want to have.

Session

La taille des cookies est limitée en taille. En effet, étant transmis entre chaque requete avec le serveur, il est important qu'ils restent petits afin de ne pas alourdir le traffic.

On limitera donc l'information portée par le cookie à une seule valeur : un ID

Une session va donc être une mémoire tampon, à l'intérieur de laquelle nous allons pouvoir lire/écrire des informations relative à un client particulier.

A session is a semi-permanent interactive information interchange between two or more communicating devices, usually a server and a client.

In particular, a login session is the period of activity between a user log-in and log-out of a system. This system could be our Operative System (yes, right now you are in the middle of a session!), or our web application.

Afin de mettre en place ce mécanisme, nous utiliserons express-session + connect-mongo en tant que cookie-store.

In the Node.js ecosystem, we can use the express-session package to generate sessions in Express. We will use it in combination with connect-mongo to store session data inside our database.

We will see how to configure express-session and connect-mongo later in this learning unit.

Log in

Authentication is the process where users provide their credentials (username/email and password) and send them to the server. The user will be authenticated if the username and password are correct. This is done through login.

In the following diagram you can see a basic login process:

Le flow de login peut ainsi être représenté :

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Once the user has been authenticated, we have to create a session to keep the user logged in. In this session we can store all the information we want. It's highly recommended to have the minimum information we need to keep the session.

If the information provided by the user is not correct, the server side has to send an error message to the user indicating that their credentials are wrong.

Authorization & Authentication

Passons à la pratique

Let's keep working with the project from the previous lesson basic-auth. In the first step of this project, we created the authorization process. In this learning unit we will complete the circle by adding authentication to the exercise.

app.js configuration

First of all we need to add the packages we will use to store user's session once they are authenticated. We will use express-session and connect-mongo packages to store the session in mongo and keep our users logged in.

First of all we have to add the packages to the project:

Installons nos packages utiles à mettre en place une session utilisateur :

$ npm install express-session connect-mongo

Now let's require both packages in the top part of our app.js file:

Initialisons-les dans app.js :

// app.js

const session    = require("express-session");
const MongoStore = require("connect-mongo")(session);

Now, we have to configure the middleware to enable sessions in Express. Remember we use app.use() to configure our middlewares:

Configurons les règlage de session :

// app.js

...

app.use(session({
  secret: "basic-auth-secret",
  cookie: { maxAge: 60000 },
  store: new MongoStore({
    mongooseConnection: mongoose.connection,
    ttl: 24 * 60 * 60 // 1 day
  })
}));

// Middleware Setup
...

The session package creates a new session middleware with the specified options. Here are the options we are using:

  • secret: Used to sign the session ID cookie (required)
  • cookie: Object for the session ID cookie. In this case, we only set the maxAge attribute, which configures the expiration date of the cookie (in milliseconds).
  • store: Sets the session store instance. In this case, we create a new instance of connect-mongo, so we can store the session information in our Mongo database.

Layout

The next step we have to do is to create the login form. We will add the file views/auth/login.hbs, and create the following form in it:

Implémentons notre template de login :

{{! views/auth/login.hbs }}

<form action="/login" method="POST" id="form">
  <h2>Login</h2>

  <label for="username">Username</label>
  <input type="text" name="username">

  <label for="password">Password</label>
  <input type="password" name="password">

  {{#if errorMessage }}
    <div class="error-message">{{ errorMessage }}</div>
  {{/if}}

  <button>Sign in</button>

  <p class="account-message">
    Don't have an account? <a href="/signup">Sign up</a>
  </p>
</form>

Note how we added the error validations, as we did in the authorization process. Actually, the form is almost the same as the sign up form.

Once the form is created, we need to create the routes to access the login page.

Routes

Now let's define the /login page to show the form to the users. Remember we have defined the authentication routes in the routes/auth.js file:

Notre route d'affichage du formulaire :

// routes/auth.js

router.get("/login", (req, res, next) => {
  res.render("auth/login");
});

Now, we can see our login form visiting the http://localhost:3000/login URL:

Remember to launch the node server before visit the page with npm run dev in the console.

Let's add the POST method to handle the form request. This request will authenticate the user if the username and passwords are correct:

Ainsi que notre route de traitement du formulaire de login :

router.post("/login", (req, res, next) => { const theUsername = req.body.username; const thePassword = req.body.password; if (theUsername === "" || thePassword === "") { res.render("auth/login", { errorMessage: "Please enter both, username and password to sign up." }); return; } User.findOne({ "username": theUsername }) .then(user => { if (!user) { res.render("auth/login", { errorMessage: "The username doesn't exist." }); return; // STOP } if (bcrypt.compareSync(thePassword, user.password)) { req.session.currentUser = user; // Save the user in the session! res.redirect("/"); } else { res.render("auth/login", { errorMessage: "Incorrect password" }); } }) .catch(err => next(err)) });
  • L20 : on utilise bcrypt.compareSync pour pouvoir comparer le mot de passe saisi à celui crypté en base
  • L22 : on stocke dans la session notre user afin de ne pas avoir à le chercher en base de nouveau lors des requetes suivantes.

req.session.currentUser = user; - Note how we create the user session; the request object has a property called session where we can add the values we want to store on it. In this case, we are setting it up with the user's information.

The presence of the user's information in the session is what we are going to use to check for a logged in user in the other parts of the app.

Once the user is logged in, we will redirect them to the main page in our site. We don't have any pages, so let's create them.

Home page

Créons la homepage vers laquelle l'utilisateur sera redirigée une fois loggué

Let's create a very simple home page with the three following links:

  • Sign up: we will direct the users to the sign up form.
  • Login: we will direct the users to the login form.
  • Protected page: the destination page will require to be authenticated to visit it. If you are not authenticated, it will redirect you to the login page.

So the home page, in views/index.hbs will have the following HTML:

le template :

{{! views/index.hbs }}

<div id="container">
  <a href="/signup">Sign up</a>
  <a href="/login">Login</a>
  <a href="/secret">Protected page (requires authentication!)</a>
</div>

Now we can access to http://localhost:3000/ URL and see the three links we just defined. We will create a protected route to handle /secret route we specified in the third link of the home page.

Protected routes

Créons maintenant une page "protégée", ie: accessible seulement aux utilisateurs connectés

First of all, let's create the view in views/secret.hbs file. We will show a message to the user indicating he is in a session:

{{! views/secret.hbs }}

<div id="container">
  <h2>You are in a session!</h2>
</div>

We want to prevent the users who are not logged from accessing this page. We can do that by writing our own middleware. In the routes/index.js we will define a ensureIsLogged middleware to call before the /secret route handler:

// routes/index.js function ensureIsLogged(req, res, next) { if (req.session.currentUser) { next(); } else { res.redirect("/login"); } } router.get("/secret", ensureIsLogged, (req, res, next) => { res.render("secret"); }); ...

Le middleware écrit L3-L9 va vérifier que l'utilisateur est loggué (en vérifiant la présence de currentUser dans req.session) avant d'accéder à n'importe quelle route de ce router.

We can use that ensureIsLogged anytime before any route handler to make sure the user is logged before rendering the page.

If we start our server and try to visit /secret, we will be redirected to the login page. Once we have logged in, we will be able to access to our secret page.

Logout

To finish up with the lesson, we will add the logout. The logout is the process where the user "closes" the connection between them and the web app. It's the opposite of the login.

Sessions package has a destroy method which, when executed, will close the user's session. To do that, we will create the following route in our routes/auth.js:

Enfin, créons une route pour se déconnecter :

// routes/auth.js router.get("/logout", (req, res, next) => { req.session.destroy((err) => { // cannot access session here res.redirect("/"); }); });

req.session.destroy permet de "vider" req.session et également effacer le cookie coté client.

As you can see, we have to make a GET request to /logout route, and once the sessions is closed, we will redirect the user to the login page again.

We can add the link in the HTML as follows:

{{! views/index.hbs }}

<a href="/logout">Logout</a>

With this, we are done with our exercise. We have completed the whole process of authorization and authentication.

Summary

In this lesson we got familiar with sessions and cookies, and how they are related to authentication. We have also seen what authentication is and how it works with authorization.

To finish up, we completed the basics of authorization form the previous lesson by completing the exercise, adding the authentication functionality.

Extra Resources