Sometimes it is nice to put a login on a small project without reinventing a wheel. Auth0 (I am unaffiliated with them) provides everything I need (and more) right out of the box. This blog post describes how to give a few users access to your single page application (SPA) and the corresponding server with minimum effort.
This blog post is based on two excellent Vue.js + Auth0 tutorials
- Vue.js — Setting up Auth0 by Matt Bradford
- Vue Auth0 quickstart guide
These are just my own notes to clarify things that I expect to forget quickly when following the above two tutorials.
Goal
- Have a small username / password database on Auth0, invite specific users by email, do not let people sign up
- Restrict some routes in the SPA to authenticated users
- Restrict some API end points to authenticated users who logged in through the web application
Auth0 setup
In Auth0 dashboard, add new client in https://manage.auth0.com/#/clients section. Choose "Single Page Application" type.
You will have three important pieces of information. First two pieces are
necessary for the client side SPA code: Domain
and Client ID
. The third
one is Client Secret
and it will be needed by the server side to validate
the token passed by the SPA to the server to make sure only the authorized
users are making API requests.
Make sure to list all domains allowed to login in the callback list. For
local testing, you could just add http://localhost:5000
or whatever port
used when running locally. If you deploy to a platform, like Zeit Now, then
give an additional widlcard domain (do NOT wildcard *.now.sh
, instead you
can wildcard specific application, for example).
1 | http://localhost:5000 |
In general, be careful with wildcards; what if someone registers an application
with matching package name and deployed to now.sh
? Maybe generate unique ID
as the package name.
Go into "Connections" and make sure only "Username-Password-Authentication" is turned on. Then go into Connections / Database menu and select "Edit". Disable Sign Ups and limit the list of applications that can use this user database to the new SPA under "Clients" tab. If you want you can require stronger passwords by setting rules under "Password Policy" tab.
Create a few users under Users menu. You can give each user an initial password, but make sure the user is created in the right "Connection" database!
SPA code
We will keep track of the authenticated state in the Vue application, and we will use AuthLock module.
1 | <script src="//cdn.auth0.com/js/lock/10.2/lock.min.js"></script> |
The SPA will ask user to login when the Vue component is ready.
1 | var App = Vue.extend({ |
The id_token
contains the authentication token we receive from the Auth0.
We can inspect it by pasting into https://jwt.io/.
We want to pass the id_token
with each Ajax request to the server. We can
do this by configuring Vue interceptor or by adding token on some API
requests.
1 | { |
Let us add a response interceptor that will make sure we handle "unauthorized" errors returned by the API. In that case, we can logout the user and return to the login screen immediately.
1 | Vue.http.interceptors.push((req, next) => { |
In the above SPA initialization code we used client id and authentication
domain. Where did we get the variable object spaConfig
?
1 | new Auth0Lock(spaConfig.AUTH0_CLIENT_ID, |
We got these values from the server. Our page requested the configuration
variables as a script spa-config.js
.
1 | <script src="//cdn.auth0.com/js/lock/10.2/lock.min.js"></script> |
The script /spa-config.js
is generated by
js-to-js and allows
avoiding injecting configuration values via inline scripts, making the web
application more secure.
We can protect the routes by requiring authenticated user for some of them
1 | // Utility to check auth status |
We have protected our client side routes, and we are passing token with API request to let the server code check if the user is authorized.
Server code
The server code needs to serve the HTML and application JavaScript; it
also needs to validate the id_token
passed with API requests.
To validate the tokens, we need to use the "Auth0 client" application's "secret" value. The main details
1 | // server.js |
After this we should put public and private routes. Plus we need to handle the case when the token check fails.
1 | // public route, for example version |
Cleaner separation of secured API calls
Some API calls might be public, for example I like having public /version
route (using version-middleware).
Thus the server can restrict which API calls are checked for valid token.
1 | // Verify Authorization header's Bearer token |
On the client side we can add the token to every /secured
Ajax call.
1 | Vue.http.interceptors.push((req, next) => { |
Why not "audience"?
Auth0 has a different work flow that can connect nicely a client
to a separate API. Unfortunately, this work flow does not work for Single
Page Application clients yet. Thus I had to use the server with Client Secret
to validate the tokens sent by the client. Please ensure the server - client
connection is protected via SSL and the client code is secured against XSS
attacks (by a strict Content-Security-Policy for example).