Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Manage Sessions Over HTTPS With Node.js And Vue.js

TwitterFacebookRedditLinkedInHacker News

A long time ago when I had been exploring session management in Node.js, I had written a tutorial titled, Session Management in Your Express.js Web Application. This was a basic tutorial that is still very functional today, however little things have changed since then when it comes to how the web works. For example, in 2015 HTTPS was never a requirement and we weren’t exposed to all the frontend web frameworks that exist today.

When you start introducing things into your web applications such as HTTPS or micro-services that operate on different origins or ports, or frontend frameworks, session management can get a little more complicated. We’re going to see how to maintain a session for a user using Node.js with Express.js on our backend and Vue.js on our frontend, in this tutorial.

Before getting too involved, there is something to note first. This particular tutorial will be using HTTPS with self-signed certificates. How you get HTTPS working within your application or what kind of certificate you try to use is up to you. However, if you need a bump in the right direction, I had written a tutorial titled, Create a Self-Signed Certificate for Node.js on MacOS, that might help. The assumption is that you also have access to the certificate files used.

Building an HTTPS Secured Node.js Application with Session Management

Because our sessions will be maintained server-side, we need to first create our backend application with Node.js and Express.js. Assuming you have Node.js installed, execute the following commands:

npm init -y
npm install express --save
npm install express-session --save
npm install cors --save
npm install body-parser --save
npm install uuid --save

While the above commands didn’t have to be split into numerous commands, what they’ll do is create a new project with the necessary dependencies. While they are all important for this project, the express-session and cors packages are the most important.

To get us up to speed with the previous article I wrote around HTTPS, create an app.js file within your project and include the following JavaScript code:

const Express = require("express");
const BodyParser = require("body-parser");
const Cors = require("cors");
const HTTPS = require("https");
const FS = require("fs");
const UUID = require("uuid");
const session = require("express-session");

var app = Express();

app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));

app.get("/session", (request, response, next) => { });
app.post("/session", (request, response, next) => { });

HTTPS.createServer({
    key: FS.readFileSync("server.key"),
    cert: FS.readFileSync("server.cert")
}, app).listen(443, () => {
    console.log("Listening at :443...");
});

You’ll notice that we have started our server with HTTPS, so the assumption is that you already have your certificates figured out. This example will have two endpoints. The first endpoint will set the session and return it to the user and the second endpoint will validate user data against the session. What I’m trying to demonstrate is that the session persists across numerous HTTP requests.

With our boilerplate code in place, let’s configure the session management in our backend. You’ll want your code to look like the following:

app.use(session({ secret: "thepolyglotdeveloper", cookie: { secure: true, maxAge: 60000 }, saveUninitialized: true, resave: true }));
app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));

Notice that we are setting a secret for the session, making it mandatory that we are secured with HTTPS and creating the session even before we try to use it. Probably the most important part here is that we are saying it is secure because both the Node.js API and the client frontend will be using HTTPS.

The first endpoint that the user will encounter will be a consumption endpoint:

app.get("/session", (request, response, next) => {
    request.session.example = UUID.v4();
    response.send({ id: request.session.example });
});

When the user requests data, the session will store a UUID value and return it back to the user. The goal here is to have the user send this data back to be validated and if it doesn’t match, we’ll throw an error. To do the validation, we can create the following endpoint:

app.post("/session", (request, response, next) => {
    if(request.body.session != request.session.example) {
        return response.status(500).send({ message: "The data in the session does not match!" });
    }
    response.send({ message: "Success!" });
});

The logic in our application is not intended to be complex. We just want to show that the session data persists across numerous requests. Do note that the session is being stored in memory on the server and that there are numerous ways to store a session. A better solution might be to store it in a database instead, so that way if the application needs to be restarted, the sessions are not cleared.

As of right now we have an API with sessions. However, there is one important thing that we’re missing that is particularly important when using micro-services. Typically our API and our client frontend will not be on the same domain or port which would create problems with cross origin resource sharing (CORS). We need to create a whitelist of origins that are allowed to send session information:

app.use(session({ secret: "thepolyglotdeveloper", cookie: { secure: true, maxAge: 60000 }, saveUninitialized: true, resave: true }));
app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));
app.use(Cors({ origin: ["https://localhost:2015"], credentials: true }));

Notice that we are using the cors package to define the possible origins and we are saying that credentials can be passed. By default the client will not be able to send session information in the request which would give our API problems. What would happen is the session would not persist between requests for a particular user because the user data isn’t being passed.

At this point in time our API should be functional and we’re ready to start designing our frontend experience.

Building a Client Frontend with the Vue.js JavaScript Framework

When it comes to the frontend technology, it doesn’t really matter, but for this example we’re going to use Vue.js. The assumption is that you have the Vue CLI installed, configured, and ready to go. From the command line, execute the following to create a new project:

vue create session-vue

The CLI will ask you a few questions, but you can use the defaults for this example. We’re only going to be making changes to the project’s src/components/HelloWorld.vue file. If you want, you can change the components to have more friendly names or change the styling, but it is out of the scope of what we want to accomplish.

Since we will be making HTTP requests, we’re going to need to install the axios package. This can be done by executing the following:

npm install axios --save

There are other ways to make HTTP requests in Vue.js, but we are going to stick with the axios way. You can view more information on consuming API data in a previous tutorial I wrote titled, Consume Remote API Data via HTTP in a Vue.js Web Application.

Open the project’s src/components/HelloWorld.vue file and include the following code:

<template>
    <div class="hello">
        <input type="text" v-model="id" />
        <button v-on:click="validate()">Send</button>
    </div>
</template>

<script>
    import axios from "axios";
    export default {
        name: 'HelloWorld',
        data() {
            return {
                id: ""
            };
        },
        mounted() {
            axios({ method: "GET", "url": "https://localhost/session", withCredentials: true }).then(result => {
                this.id = result.data.id;
            }, error => {
                console.error(error.response.data);
            });
        },
        methods: {
            validate() {
                axios({ method: "POST", "url": "https://localhost/session", data: { session: this.id }, headers: { "content-type": "application/json" }, withCredentials: true }).then(result => {
                    alert(JSON.stringify(result.data));
                }).catch(error => {
                    console.error(error.response.data);
                });
            }
        }
    }
</script>

<style scoped></style>

There are a few things happening in the above code. First we’re creating an id variable to be used in our form. This id is populated from the axios request to our API when the component mounts. The id is bound to the form and a method is bound to our button for triggering the second request.

The important pice to note here is the withCredentials property that we are adding to the requests. This allows for our session information to be sent, otherwise the server wouldn’t have a way to distinguish users within incoming requests.

If you try to validate with the data that returns from the first request, you’ll get a success alert. If you change the data in the form and try to validate, it will fail.

Because we are using HTTPS, we cannot use the built in server that comes with Vue.js for testing. We need to be testing with a valid certificate because our backend is expecting it. There are many different ways to accomplish this task, but for simplicity purposes, we’re just going to use an application called Caddy Server. Assuming you have Caddy Server, you can create a Caddyfile at the root of your project:

localhost:2015 {
    root dist
    tls server.cert server.key
}

The above configuration says that we will be serving whatever ends up in the dist directory and we are using HTTPS with our self-signed certificates. These certificate files must be at the root of our project as well. To run our project, we can execute the following:

npm run build
caddy

Again, the above assumes you’re choosing to use Caddy Server. You can choose to use NGINX or Apache httpd, or something else entirely. However, the setup will be different between the applications. The application in our example will be served at https://localhost:2015, which is what we defined in our backend.

If you’d like to learn more about using Caddy Server, check out a previous tutorial I wrote titled, Serve Your Web Applications with Minimal Effort Using Caddy.

Conclusion

You just saw how to manage user sessions in a Node.js and Vue.js application secured with HTTPS and operating on different origins. In short, the focus points of this tutorial were around enabling sessions and allowing cross origin resource sharing (CORS) for them in Node.js, then being able to make requests in a Vue.js application while sending credential information.

As previously mentioned, the purpose was not to demonstrate configuring HTTPS in Node.js because this is something I had already discussed in a previous tutorial. Likewise, I had also demonstrated how to do standard HTTP requests with Vue.js in a previous tutorial as well.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.