0% found this document useful (0 votes)
106 views13 pages

How It Works: Single-Page App Authentication Using Cookies

This document provides instructions for authenticating a single-page application using cookies. It explains how authentication works by retrieving tokens from an authorization server and storing them in a cookie. It then details implementing this approach with a Node.js/Express backend, including setting up middleware, handling invalid cookies, and providing an example application configured to work with Auth0 for authentication.

Uploaded by

NIck
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
106 views13 pages

How It Works: Single-Page App Authentication Using Cookies

This document provides instructions for authenticating a single-page application using cookies. It explains how authentication works by retrieving tokens from an authorization server and storing them in a cookie. It then details implementing this approach with a Node.js/Express backend, including setting up middleware, handling invalid cookies, and providing an example application configured to work with Auth0 for authentication.

Uploaded by

NIck
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

Single-Page App Authentication Using Cookies

In this article
Securing a single-page application (SPA) can be a challenge. However, if your SPA:

• is served to the client using your own backend


• has the same domain as your backend
• makes API calls that require authentication to your backend

Then you can simplify your implementation by using cookies to authenticate your
SPA. In the following guide you'll find an overview of this approach as well as a
sample implementation using Node.js.

How it works
The steps below show how tokens are retrieved and used. In this approach,
the Form Post Response Mode is used instead of a traditional Authorization Code
flow. This is because Form Post Response Mode is a simpler way to implement login
when it’s your own resource you are requesting access to.

1. The user accesses a protected route using the browser, or performs some
action that requires an authentication step to be initiated (such as clicking on
a Login button)
2. The browser client redirects to a /login route on the backend, or to the
protected route depending on what the user did
3. The backend constructs a request to the authorization
server’s /authorize endpoint and redirects the browser client there
4. The user is prompted to authenticate themselves using whatever method the
authorization server presents
5. The authorization server POSTs the tokens to the redirect URI as a URL-
encoded form post. The backend is able to retrieve those tokens by parsing
the body data.

At this point, the user is authenticated and the backend has the required tokens. A
cookie can now be created to represent this state on the client. The client browser is
then redirected to a route that serves the SPA and also receives the authentication
cookie.

From now on, this cookie is traded between the client and backend when API calls
are made using an AJAX call. On each request, the backend verifies if the cookie is
still valid and if so, allows the request to continue.

Dealing with invalid or missing cookies


When implementing this approach you'll need to handle cases where the
authentication cookie is invalid or missing. The API call to the backend from the
client happens in the background, so the client has to deal with any response from
the server indicating the user should reauthenticate.

In the following sample application, this case is handled in a naive way by prompting
the user to reauthenticate if the API call results in a 302 Redirect result. The 302
occurs because, upon unsuccessful validation of the cookie, the server tries to
redirect to the authorization endpoint of the authorization server and sends this
response to the client.

Example: Authenticating a SPA using cookies


The example application uses Node.js and Express to demonstrate the concepts
covered above.

Prerequisites
To follow along, make sure you have the latest version of Node installed.

Once Node is installed, download or clone the source code and open the project
folder inside a terminal window.

// Clone the tutorial respository using SSH

$ git clone [email protected]:auth0-blog/spa-cookie-demo

// ... or if you use HTTPS:

$ git clone https://github.com/auth0-blog/spa-cookie-demo.git

// Move into the project directory

$ cd spa-cookie-demo

Was this helpful?Yes/No

The master branch represents the state of the application before any authentication
is added. If you would like to refer to the final version of the
application, checkout the with-oidc branch:

$ git checkout with-oidc

Was this helpful?Yes/No

Initialize the Node.js application


Install the application dependencies by running npm install from your terminal
window. To run the application, use npm run dev. This starts the Express server. Go
to http://localhost:3000 in your browser to view the application.

The development server uses nodemon , which automatically restarts whenever it detects
any file changes.

Explore the application


With the application open at http://localhost:3000, click the Call API button. You
should see a message displayed on the
screen.
Note that you were able to make the API call without being logged in. Let's fix that
by adding authentication some middleware that requires the user to authenticate
before the API call can be made.

Adding authentication middleware


Inside the terminal, run the following command to install the additional libraries:

$ npm install express-session express-openid-connect

Was this helpful?Yes/No

• express-openid-connect — OpenID Connect middleware for Express.


Creates authentication sessions and protects application routes
• express-session — session middleware for Express; required by express-
openid-connect Next, open server/index.js and add in the middleware
libraries underneath the existing require statements at the top of the file:

// server/index.js

// Other 'require' statements...

// NEW - bring in our new middleware libraries

const { auth } = require("express-openid-connect")

const session = require("express-session")


Was this helpful?Yes/No

The express-session middleware can now be configured by adding the following


code above the existing configuration for body-parser further down the file:

// server/index.js

// Existing config for morgan

app.use(morgan("dev", {

stream: {

write: m => debug(m)

}))

// NEW - add this configuration for express-session

app.use(

session({

secret: process.env.APP_SECRET || "keyboard cat",

resave: false,

saveUninitialized: true,

cookie: {

secure: process.env.NODE_ENV === "production",

httpOnly: true

})

// Existing config for body-parser

app.use(bodyParser.urlencoded({

extended: false

}))

Was this helpful?Yes/No

Finally, add in the configuration for the express-openid-connect middleware. The


location of this is important; it should be inserted after the configuration for the static
file server, but before the definition of the API routes. This ensures the static files can
be served without requiring authentication, but the API routes are secured:
// server/index.js

// Existing config for the static file server

app.use(express.static(join(__dirname, "..", "public")))

// NEW - Configure the OpenID Connect middleware

app.use(

auth({

required: req => req.originalUrl !== "/"

})

// Existing config for the API routes

app.use("/api", require("./api"))

Was this helpful?Yes/No

Note that in this case, the authentication step is only applied if the request is for
something other than the homepage. This lets us show some kind of UI even if the
user is not logged in. We can display a "log in" button if they have not yet
authenticated, or some other UI if they have. After these changes, your server script
should more-or-less look like this:

require("dotenv").config()

const express = require("express")

const helmet = require("helmet")

const morgan = require("morgan")

const debug = require("debug")("app:server")

const session = require("express-session")

const bodyParser = require("body-parser")

const {

auth

} = require("express-openid-connect")

const {

join

} = require("path")

const app = express()


app.use(helmet())

app.use(morgan("dev", {

stream: {

write: m => debug(m)

}))

// Set up express-session (required by express-openid-connect)

app.use(

session({

secret: process.env.APP_SECRET || "keyboard cat",

resave: false,

saveUninitialized: true,

cookie: {

secure: process.env.NODE_ENV === "production",

httpOnly: true

})

app.use(bodyParser.urlencoded({

extended: false

}))

app.use(express.static(join(__dirname, "..", "public")))

// Set up authentication middleware, only strictly required if

// the request isn't for the home page

app.use(

auth({

required: req => req.originalUrl !== "/"

})

app.use("/api", require("./api"))
app.get("/*", (req, res) => {

res.sendFile(join(__dirname, "..", "public", "index.html"))

})

const port = process.env.PORT || 3000

app.listen(port, () => debug("Application listening on port " + port))

Was this helpful?Yes/No

Setting up the environment


In order for the application to work with authentication, express-openid-
connect requires some environment variables to be present. For this application,
these variables can be specified in a .env file. Create a .env file in the root of the
project directory and populate it with the following:

ISSUER_BASE_URL=<YOUR OIDC URL>

CLIENT_ID=<YOUR OIDC CLIENT ID>

BASE_URL=http://localhost:3000

Was this helpful?Yes/No

Setting up an Auth0 app


If you don't already have an Auth0 account, you can sign up for a free Auth0 account
here. Next, set up an Auth0 Client and API so Auth0 can interface with your app and
API.

1. Go to your Auth0 Dashboard and click the Create Application button.


2. Name your new app, select Regular Web Applications and click
the Create button.
3. In the Settings for your new Auth0 app,
add http://localhost:3000/callback to the Allowed Callback URLs.
4. Add http://localhost:3000 to the Allowed Logout URLs.
5. Click the Save Changes button.
6. If you'd like, you can set up some social connections. You can then enable
them for your app in the Application options under the Connections tab. The
example shown in the screenshot above utilizes username/password
database, Facebook, Google, and Twitter. On the Settings screen, note the
domain and client ID settings at the
top.
These are the two values that need to be configured as part of the
application. Reopen the .env file and set these values:

LOG IN
to configure this snippet with your account

ISSUER_BASE_URL=YOUR_DOMAIN

CLIENT_ID=YOUR_CLIENT_ID

BASE_URL=http://localhost:3000

Was this helpful?Yes/No

Running the application


With the server and environment configuration done, find your browser window that
has the application open. If you've closed the browser and stopped the server, run
the following from the terminal to restart the application

$ npm run dev

Was this helpful?Yes/No

Then open http://localhost:3000 in the browser. From a user interface perspective,


the application should look the same. However, this time when the Call API button is
clicked, you should receive a warning that the user is not logged in. Also note that
you do not see the "Hello, World" message as before, since the call to the API has
been
rejected.

Click the "Log in now" to login. Once you have been authenticated, you'll return to
the application and see an updated UI that reflects your newly-logged in state. You
should be able to press the Call API button once more to invoke an API call to the
server, and it now
works!
You can click the "Profile" link at the top of the page to show user information
retrieved from the ID
token.

You might also like