How It Works: Single-Page App Authentication Using Cookies
How It Works: Single-Page App Authentication Using Cookies
In this article
Securing a single-page application (SPA) can be a challenge. However, if your SPA:
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.
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.
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.
$ cd spa-cookie-demo
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:
The development server uses nodemon , which automatically restarts whenever it detects
any file changes.
// server/index.js
// server/index.js
app.use(morgan("dev", {
stream: {
}))
app.use(
session({
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true
})
app.use(bodyParser.urlencoded({
extended: false
}))
app.use(
auth({
})
app.use("/api", require("./api"))
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 {
auth
} = require("express-openid-connect")
const {
join
} = require("path")
app.use(morgan("dev", {
stream: {
}))
app.use(
session({
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true
})
app.use(bodyParser.urlencoded({
extended: false
}))
app.use(
auth({
})
app.use("/api", require("./api"))
app.get("/*", (req, res) => {
})
BASE_URL=http://localhost:3000
LOG IN
to configure this snippet with your account
ISSUER_BASE_URL=YOUR_DOMAIN
CLIENT_ID=YOUR_CLIENT_ID
BASE_URL=http://localhost:3000
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.