TL;DR: In this article, you're going to build an Express API and then make API calls to it from an existing Vue.js application. You'll then secure this API by only allowing users who have an access token to view certain data. Here is the GitHub repository with the final code if you'd like to take a peek now.
Requirements
This application is using the latest versions (at the time of writing) of the following:
You're starting with a pre-built Vue application that currently has its data hard-coded in. You'll modify it so that instead of using hard-coded data, you'll build an API and pull it from there. If you'd like more background about how this was built, check out the previous tutorial here.
Installing Node and npm
Node.js is a runtime environment that uses JavaScript on the server. This makes it very popular in modern JavaScript applications because you can use JavaScript across the board: on the frontend and backend.
Node.js comes with npm
, a Node package manager. You can download Node.js here.
Let's get started!
Recap of the Vue.js App
In case you didn't go through the previous tutorial, here's a recap of what the requirements were:
- Display a list of events (public)
- Allow users to register/sign in
- Display individual event details (must be logged in)
The caveat here was that instead of getting the data from one central API, it was just stored in the component itself with Vue's data function. Because the data was saved in the JavaScript since we didn't have an API, there was no way to properly secure it. In the application's current state, the user would be kicked to the login page if they tried to view individual event details, but they could still manually search through the JavaScript to see the page content. In this tutorial, you're going to modify that app so that it pulls from an API.
"Learn how to modify a Vue application to pull data from a secure API."
Tweet This
Application Setup
You're going to make a separate folder for the server code that you'll be working on. Keeping the client code (Vue app) and server code (Express app) in the same parent folder makes it easier to switch back and forth while still keeping them separated.
First, make a new folder called vue-express-events-auth
that will serve as the parent folder for client
and server
. Since the client side of this application is already built, you're just going to clone the Vue application repo and call it client
. Then make a new folder inside vue-express-events-auth
and call it server
.
mkdir vue-express-events-auth
cd vue-express-events-auth
git clone https://github.com/auth0-blog/vue-events-auth client
mkdir server
Here's what the folder structure looks like:
├───vue-express-events-auth
└── client
└── server
Client setup
Now that everything is split up nicely, it's time to pull the dependencies into the client app. In your terminal, switch into the client
folder and run:
npm install
Configuring Auth0
This will create a node_modules
folder and all of the app's dependencies will be installed into it.
The previous tutorial used Auth0 to add authentication to the Vue.js app. Auth0 provides a simple way to integrate authentication and authorization into any application. Most of this is already configured for you in the existing repo, but you'll need to sign up for a free Auth0 account to update some of the configuration values.
Once you have your free account, head to the Auth0 dashboard to set up the Auth0 application for the Vue app.
Click on the red button in the top right corner that says "Create Application". For the name, you can enter "Vue Events" or anything you'd like. Then select "Single Page Web Applications" and click "Create".
Next, click into "Settings". This is where you'll fill out some important information that allows the Auth0 application to connect to your Vue application. Fill these out as follows:
- Allowed Callback URLs —
http://localhost:8080
- Allowed Logout URLs —
http://localhost:8080
- Allowed Web Origins —
http://localhost:8080
These must match the development URL exactly! If you move your application into production in the future, you'll have to update these values to match the production URL.
Now the last thing you need to do before running the existing application is configure it to use your Auth0 variables from this dashboard.
Create a new file called auth_config.json
in the root of the client
folder and paste in the following:
// client/auth_config.json
{
"domain": "your-domain.auth0.com",
"clientId": "yourclientid"
}
Now you need to fill in the domain
and clientId
values for the auth_config
file. In your Auth0 dashboard, make sure you're on the Application page that you just created ("Vue Events").
- Copy the value for "Domain" and paste it into
domain
inauth_config.json
- Copy the value for "Client ID" and paste it into
clientId
inauth_config.json
Once that's finished, you can start the application by running:
npm run serve
Make sure you're in the client
folder when you run the above command.
See it in action at http://localhost:8080/!
You can keep this running in your terminal in the background throughout the rest of the tutorial.
Server setup
Next, it's time to focus on the server side of things. Inside the server
folder, make two files: package.json
and server.js
:
Open up the server/package.json
file in your code editor and paste in the following:
{
"name": "events-api",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"nodemon": "^1.19.4"
}
}
This will hold all the dependencies required for this project, which will be covered in more detail soon. Switch back to the terminal (still in the server
folder) and install them with:
npm install
Now you need to add some basic boilerplate so that you can get the server up and running. Open up server/server.js
and paste in the following:
// server/server.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const port = 8000;
app.use(bodyParser.json());
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.send(`Hi! Server is listening on port ${port}`)
});
// listen on the port
app.listen(port);
This server.js
file will be the entry point for the Express app. Once it's time to run the app, this file will be read and executed. So what's going on here?
First, you're requiring all of those dependencies that you installed earlier with that package.json
file:
- Express — Express is a lightweight framework for Node.js. It will greatly simplify how you make HTTP requests.
- body-parser —
body-parser
is a Node.js middleware that helps us parse incoming HTTP requests by exposing them onreq.body
. - CORS — If you've ever worked with an API before, you might have come across this pesky message:
The CORS policy for this site does not allow access from the specified Origin.
. The Express CORS package enables CORS support, which allows you to manage access to your API from other domains.
In the next lines, you're using all of those dependencies with app.use()
.
After that, you're setting up your first GET
request!
app.get('/', (req, res) => {
res.send(`Hi! Server is listening on port ${port}`)
});
This GET
request will be covered more in-depth once you create all of the HTTP requests, but for now, this is just used to make sure the server is running.
If you look back in that server/package.json
file, there's an object named scripts
that has this entry:
// ...
{ "start" : "nodemon server.js" }
// ...
When you run the app, Nodemon will watch for any changes and automatically restart the server when a change is detected. This saves a lot of development time since you won't have to refresh the page manually.
In a new tab in your terminal, navigate to the server
folder and enter in the following:
npm start
This will start up the server. You can check that it's working by going to http://localhost:8000 in your browser. You should see Hi! Server is listening on port 8000
. If not, feel free to reach out in the comments and we can help you debug!
Creating the API
Now that the folder structure for both the client and server is set up and both apps are running, it's time to get to work on building the API.
For simplicity, you're just going to store the events
data in a variable instead of connecting to a database to pull it.
Add the following into the server.js
file right before app.get()
:
// server/server.js
// dependencies
// ...
// use dependencies
// ...
// mock data to send to our frontend
let events =
[
{
id: 1,
name: 'Charity Ball',
category: 'Fundraising',
description: 'Spend an elegant night of dinner and dancing with us as we raise money for our new rescue farm.',
featuredImage: 'https://placekitten.com/500/500',
images: [
'https://placekitten.com/500/500',
'https://placekitten.com/500/500',
'https://placekitten.com/500/500',
],
location: '1234 Fancy Ave',
date: '12-25-2019',
time: '11:30'
},
{
id: 2,
name: 'Rescue Center Goods Drive',
category: 'Adoptions',
description: 'Come to our donation drive to help us replenish our stock of pet food, toys, bedding, etc. We will have live bands, games, food trucks, and much more.',
featuredImage: 'https://placekitten.com/500/500',
images: [
'https://placekitten.com/500/500'
],
location: '1234 Dog Alley',
date: '11-21-2019',
time: '12:00'
}
];
Just to recap in case you skipped the previous Vue login tutorial, the events
variable is an array of objects, where each object is a single event.
The Vue application uses this data in 2 ways:
- To get a list of all events
To get the details of a single event
Since the Vue client is going to make an API call to get that data, you have to build out an endpoint for each of those!
Get all events
Open up server/server.js
and add the following below the events
variable:
// server/server.js
// let events = [...]
// ...
// NEW -- get all events
app.get('/events', (req, res) => {
res.send(events);
});
In the code snippet above, you're making a GET
request using the Express get()
method.
The first parameter is the route, /events
, that you'll use to hit this endpoint. The next parameter is a callback function that's called when a request is made to the /events
path. This callback function is waiting for a request to this endpoint and once you make the request, the callback function is executed.
This callback function also has two parameters: req
and res
:
req
— Contains properties about the request being made such as the method (GET
), body, HTTP headers, and more. You can see the full list of properties here.res
— Contains the HTTP response that is sent after receiving a request. It can take advantage of several different methods. A common one isres.send()
, which will handle sending this response back after the request is made.
When a GET
request is made to /events
, the callback function is called and the events
data is sent back in the response with res.send(events)
.
Make sure your Express server is still running and then open up http://localhost:8000/events in the browser. You should be able to see all of the events
data returned!
Get a single event
Next, you need to create an endpoint that returns a single event by id
from the events
array.
Paste the following into server.js
right below app.get(/events)
:
// server/server.js
app.get('/events/:id', (req, res) => {
const id = Number(req.params.id);
const event = events.find(event => event.id === id);
res.send(event);
});
This will respond to a request that hits the route /events/:id
. This route includes something called a route parameter, :id
. When the client makes a request to this endpoint, it's going to specify the event it wants back by replacing :id
with the actual id
of the event.
For example, if you want to get the data for the event with id
of 1
, then you make a request to /events/1
.
Typically you would end up querying a database to fulfill this request, but since you have the events
array in this file for simplicity, you're just going to parse the object for the event where the id
matches the one in the request.
You can access the specified id
with the params
property: req.params.id
. You're then casting that to a Number
type so that it can be properly matched as you search the events
array.
You're also using the JavaScript find()
method to grab the event object whose id
matches the id
in the request. Once you have that, you're sending the event data back in the response.
Head to http://localhost:8000/events/1 in your browser to test it out. This is the data you should see for the single event:
{
id: 1,
name: 'Charity Ball',
category: 'Fundraising',
description: 'Spend an elegant night of dinner and dancing with us as we raise money for our new rescue farm.',
featuredImage: 'https://placekitten.com/500/500',
images: [
'https://placekitten.com/500/500',
'https://placekitten.com/500/500',
'https://placekitten.com/500/500',
],
location: '1234 Fancy Ave',
date: '12-25-2019',
time: '11:30'
}
Great! Now that your API is ready to go, you can switch back to the client side and pull this data into your Vue app.
Connecting the Vue Client to the Express Server
You're going to create a service, EventService
, to accomplish this. Create a new folder inside of client/src
and name it services
. Then inside of that, create a new file called EventService.js
.
Open up the EventService.js
file and paste this in:
// client/src/services/EventService.js
import axios from "axios"
export default {
async getEvents() {
let res = await axios.get("http://localhost:8000/events");
return res.data;
},
async getEventSingle(eventId) {
let res = await axios.get("http://localhost:8000/events/" + eventId);
return res.data;
}
}
You're going to use a package called axios, which is a promise-based HTTP client that will assist you in making HTTP requests.
First, you need to install it. Make sure you're in the client
folder and run:
npm install axios
The first method, getEvents()
, sends a GET
request to the server at the /events
endpoint. Then it waits for the data to come back (res.data
). The data you're expecting is the same data that you saw earlier when you set up the Express API and hit that /events
route.
The next method, getEventSingle(eventId)
, makes a GET
request to /events/:id
, where :id
is some parameter you're passing in.
Now that the client can make API calls, you have to go through the specific components that need this data and let them use this EventService
.
HTTP requests from components
First, you need to work on pulling dynamic data into a single event component.
Single Event Component
Make sure your Vue application is still running, npm run serve
, and then open up the event page in the browser for the event with id
of 1: http://localhost:8080/event/1. If it prompts you to log in (as it should), go ahead and sign in now.
The goal is to keep this page looking exactly the same, but cut down the component logic. You'll do this by utilizing EventService
to get the data.
First, open up the EventSingle
component in src/views/EventSingle.vue
and scroll down to the bottom where the <script>
tag starts. You're going to import EventService
, initialize the empty event
object, replace the created()
method, and create a new methods
object. Update the script
section as follows:
<script>
// NEW - import EventService
import EventService from '@/services/EventService.js';
export default {
name: 'EventSingle',
data() {
// NEW - initialize the event object
return {
event: {}
}
},
created() {
this.getEventData(); // NEW - call getEventData() when the instance is created
},
// NEW
methods: {
async getEventData() {
// Use the eventService to call the getEventSingle() method
EventService.getEventSingle(this.$route.params.id)
.then(
(event => {
this.$set(this, "event", event);
}).bind(this)
);
}
}
}
</script>
Instead of using the hard-coded events
array, you're now making a call to the API to get the data.
Right after the Vue instance is created, you're calling this.getEventData()
, which uses the getEventSingle()
method that you defined in eventService.js
. Then you're passing it the event id
that is plucked from the route parameter (http://localhost:8080/event/1
). Once the data is returned from the service, you're assigning it to the event
variable defined in data()
.
Now there's one more mystical-looking line here:
this.$set(this, "event", event);
What does this do (pun intended :P)? this
is referring to the Vue instance. You're using the $set
method on the Vue instance to set the event
object as a property on the Vue instance. This allows Vue to detect changes to the data, which is essential if you want your application to be reactive.
Now, if you look at the single event page, it should still look the same, but you've cut your component code down significantly!
Event List Component
There is still one other component that's using hard-coded data: EventsList.vue
. This is the component that's pulling in all of the list data to populate the homepage. Each individual card on the homepage then uses the EventSingle
component that you just modified to show the data for that specific event.
Open up src/components/EventsList.vue
and modify it to use EventService
. Replace everything between the script
tags with:
<!-- client/src/views/EventSingle.vue -->
<script>
import EventCard from "@/components/EventCard";
import EventService from '@/services/EventService.js'; // NEW
export default {
name: "EventsList",
components: {
EventCard
},
data() {
return {
event: {},
events: []
};
},
created() {
this.getEventsData(); // NEW - call getEventData() when the instance is created
},
// NEW
methods: {
async getEventsData() {
// NEW - Use the eventService to call the getEvents() method
EventService.getEvents()
.then(
(events => {
this.$set(this, "events", events);
}).bind(this)
);
}
}
};
</script>
This is pretty similar to what you did in the EventSingle
component, except this time you're calling the getEvents()
method from EventService
.
Now if you go back to the homepage, everything should look the same! And if you ever need to modify data, you can do it in one spot now: the Express API.
Securing the Express API
In the previous article, one of the requirements of the application was that only authenticated users should be able to access all of the details of an event (beyond those shown on the homepage).
Right now, if you go to a single event page, you're kicked to the login page if you aren't already logged in. So you're done, right? Well... not so fast.
Remember when you were testing the API earlier before modifying the Vue application? You went directly to the API endpoint in your browser and you were able to see the event details. Try it out by going to http://localhost:8000/events/1 in your browser. You can also use Postman to create the GET
request (or any HTTP requests) and access the same data, as shown in the image below.
The authentication requirement that you implemented in the previous article only stopped the user from accessing that specific view at /events/:id
, but when you're calling an API, you need to protect that separately.
"Even if a page is restricted through your Vue client, someone may still be able to access the data if you're receiving it from an insecure API."
Tweet This
You're going to accomplish this using the free Auth0 account that you set up earlier.
Creating the API in the Auth0 dashboard
Head to your Auth0 management dashboard and click on "APIs" in the menu on the left-hand side.
Then click on the big red "Create API" button in the top-right corner to create the API that you'll be connecting to your Express application.
Fill out the following information and then press "Create":
- Name:
Vue Express API
or something similar - Identifier:
https://vue-express-api.com
or something similar
Note: This is just an identifier for the API. We recommend using a URL, but it will never be called and doesn't have to be a publicly available URL.
- Signing Algorithm:
RS256
Integrating Auth0 into the Express App
The end goal is to require the client to have a validated access token every time it makes a request to the API. You'll handle getting that access token later on in the client code.
Right now, you need to switch your focus back to that server/server.js
file on the Express side. The client is going to make a request to the API and in that request will be an access token. You need to modify the server.js
file to make sure that the token is valid.
Here's what you need to do to secure the API:
Create a middleware function to validate the access token
Enable the middleware in the routes you want to be protected
Once that's done, you'll switch back to the client and work on issuing the access token and adding it to the API request.
In your terminal, make sure you're in the server
folder and run:
npm install express-jwt jwks-rsa
express-jwt
— middleware used to validate JWTs (JSON Web Tokens)jwks-rsa
— retrieves RSA signing keys from a JWKS (JSON Web Key Set) endpoint
JWT — A JSON Web Token is an open standard that defines a compact and self-contained way to securely transmit information between two parties as a JSON object. It's small enough that it can be sent in an HTTP header and it also contains all information needed about an entity, which means you don't need multiple database queries to validate it.
JWKS — A JSON Web Key Set is a set of keys that contains the public keys needed to verify any JWT issued by an authorization server (Auth0 in this case). Auth0 created a JWKS endpoint for you after you created your tenant, which can be found at https://YOUR_DOMAIN/.well-known/jwks.json
.
This is a more detailed look at what you're going to do in the server.js
file:
- Import the
express-jwt
andjwks-rsa
dependencies - Add your configuration variables from the Auth0 dashboard:
domain
andaudience
- Create the middleware,
checkJwt
, to validate the JWT usingexpress-jwt
- Get signing keys using
jwksRsa
, the key identifier from the header claim (this shows which key was used), and your JWKS endpoint from Auth0 - Validate the audience, issuer, and signing algorithm (these values should match those in your Auth0 dashboard)
- Get signing keys using
- Add the middleware to all of the routes that you want to protect
Open up server/server.js
and modify it as follows. The new code is marked with a NEW
comment.
// server/server.js
// import dependencies
const jwt = require("express-jwt"); // NEW
const jwksRsa = require("jwks-rsa"); // NEW
// app.use()
// NEW
// Set up Auth0 configuration
const authConfig = {
domain: "YOUR_DOMAIN",
audience: "YOUR_API_IDENTIFIER"
};
// NEW
// Create middleware to validate the JWT using express-jwt
const checkJwt = jwt({
// Provide a signing key based on the key identifier in the header and the signing keys provided by your Auth0 JWKS endpoint.
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${authConfig.domain}/.well-known/jwks.json`
}),
// Validate the audience (Identifier) and the issuer (Domain).
audience: authConfig.audience,
issuer: `https://${authConfig.domain}/`,
algorithms: ["RS256"]
});
// let events = [...]
// get all events
app.get('/events', (req, res) => {
res.send(events);
});
// NEW (updated)
// For this app, we only want to protect the route that returns the details of an event
app.get('/events/:id', checkJwt, (req, res) => {
const id = Number(req.params.id);
const event = events.find(event => event.id === id);
res.send(event);
});
Make sure to modify domain
and audience
inside authConfig
. These values can be found in your Auth0 dashboard where domain
is found in any of your Applications and audience
is the Identifier
value under "APIs" > "Vue Express API" (or whatever you named it).
Just to recap, whenever a route is hit that has the checkJwt
middleware, the process of checking and validating the token begins.
You created the checkJwt
middleware with express-jwt
, which decodes the token and then passes the request and payload to jwksRsa.expressJwtSecret()
. Then you use the jwks-rsa
package to download all of the signing keys from your Auth0 JWKS endpoint. It will check if any of the signing keys from your JWKS endpoint match the key identifier that came through in the header. If so, you pass the correct signing key to express-jwt
and it continues its validation of the token. If not, an error is thrown.
You're all ready to handle when a token comes through! Now you need to switch to the client so that you can create and attach tokens to your HTTP requests.
Making Vue HTTP Requests with an Access Token
First, you need to tell Auth0 to issue a JSON Web Token to your client.
Open client/auth_config.json
and add a new value for the audience
. This can be found in the Auth0 dashboard under "APIs" > Your API ("Vue Express API") > Identifier
. It's the same value you used on the Express side.
{
"domain": "YOUR_DOMAIN",
"clientId": "YOUR_CLIENT_ID",
"audience": "YOUR_API_IDENTIFIER"
}
This will tell Auth0 which API you want to access.
Next, open up client/src/main.js
and add the audience
value to the plugin. Make sure you add it to the import at the top as well.
// client/src/main.js
// NEW
// Import the Auth0 configuration
import { domain, clientId, audience } from "../auth_config.json";
// ...
// Install the authentication plugin here
Vue.use(Auth0Plugin, {
domain,
clientId,
audience,
onRedirectCallback: appState => {
router.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname
);
}
});
Now you just need to add the access token to your API calls!
You're going to modify the axios
request for getEventSingle(eventId)
so that when the request is made, the token is sent in the Authorization header along with it. The token will be passed in from the EventSingle
component. It's coming from the global Auth0Plugin
that was configured in the previous article, but this can only be accessed by Vue components, so you have to get the token from inside the component and pass it to the service to make the HTTP request.
Open up client/src/services/EventService.js
and modify getEventSingle()
as follows:
// client/src/services/EventService.js
async getEventSingle(eventId, accessToken) {
let res = await axios.get("http://localhost:8000/events/" + eventId, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return res.data;
}
Now the last thing to do is modify that EventSingle
component to get the access token. Open up client/src/views/EventSingle.vue
and update methods
as follows:
// client/src/views/EventSingle.vue
// UPDATED - Get the access token from the auth wrapper and pass it
// to EventService
methods: {
async getEventData() {
// Get the access token from the auth wrapper
const accessToken = await this.$auth.getTokenSilently()
// Use the eventService to call the getEventSingle method
EventService.getEventSingle(this.$route.params.id, accessToken)
.then(
(event => {
this.$set(this, "event", event);
}).bind(this)
);
}
}
You're grabbing this token from the auth wrapper in client/src/auth/index.js
and sending it through to the service.
When a user goes to a single event page, they'll be kicked to the login page if unauthenticated. This was done in the previous Vue Login tutorial by modifying the router file to run the authGuard
before the user can access that route.
If you're following this tutorial to secure your own API, make sure you include beforeEnter: authGuard
in the routes you want to protect. You can see an example of this in client/src/router/index.js
.
Now it's time to test it out! Go to http://localhost:8080/event/1
and if you're not logged in, you should be kicked to a login screen.
Once you sign in, you'll be issued an access token. The client will then make a request to the API with that access token, the server will validate it, and if it's valid, it will pass the data back!
Just to confirm that the access token is required, run that same HTTP request from earlier through Postman.
Perfect! No authorization token was provided, so the data isn't returned.
This time, include an Authorization header with the Bearer token in the request. To quickly get the access token, add console.log(accessToken)
to the app right after it's declared. Then refresh and you'll now see the access token in the console. Make sure to delete this code once you're done. This is a valid token, so the event data should be returned.
And it is! So as you can see, a valid access token is now required to view the single events data.
Of course in this instance, people could always go to the publicly available endpoint, http://localhost:8000/events
and view all of the data there, which includes specific event details, but this application was just meant to demonstrate how you can protect specific routes.
If you wanted to, you could easily go back into the server.js
file and add the checkJwt
middleware to protect the route that returns all events as well. In a real application, you'd likely be pulling this data from a database, so the private data would probably include extra information not available in a public endpoint anyway.
Wrap Up
If you made it to the end, congratulations! Let's just recap all the things you covered:
- Creating an Express API
- Configuring an API in the Auth0 dashboard
- Making HTTP requests from a Vue.js app to an API
- Securing API endpoints using Express and Vue.js
You can download the full GitHub repository with the final code here. I hope this was helpful and of course, feel free to ask any questions below! Thanks for reading!