A small guide to authentication and security for SPA
Tags: webdev, javascript
Published: 2018-10-25 19:24:46

---
title: A small guide to authentication and security for SPA
published: true
description: Secure cookies, CORS, XSS, CSRF and other confusing abbreviations
tags: [webdev, js]
cover_image: https://thepracticaldev.s3.amazonaws.com/i/0qu7lujii9twpqute5y2.jpg
---

This is by no means is an exhaustive guide, just for you get started.

**Setup**: let's assume we want to build new SPA deployed to `m.example.com`, also we have an old application, for example, Ruby on Rails, deployed to `www.example.com`. The new application will be a static website, e.g. we will only have assets (JS, HTML, CSS, images) deployed there (it could be an application with backend and SSR, but let's omit this for simplicity). Also, we will have `api.example.com` as API endpoint for our SPA application.

## Shared sessions

We want to share sessions across new and old applications. To do this we need to use cookies at the root domain - HTTP headers for cookies can look like this:

```
set-cookie: SID=...; Domain=.example.com
```

Pay attention to the dot at the beginning of the domain. This way browser will send cookies to all of our subdomains e.g. `m.example.com`, `www.example.com`, `api.example.com`. Once the user authenticates in one of our services their will be authenticated everywhere.

## Security for cookies

All of those considerations are for `api.example.com` and `www.example.com`.

### `HttpOnly`

[`HttpOnly` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies) disallows access to cookies for JavaScript to prevent hijacking of the session through XSS.

```
set-cookie: SID=...; HttpOnly
```

### `Secure`

[`Secure` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies) instructs the browser to send cookies only through HTTPSto prevent hijacking of the session through man in the middle attack. (Attack still possible if the attacker will be able to fake certificate)

```
set-cookie: SID=...;  Secure
```

### `SameSite`

[`SameSite` directive](https://www.netsparker.com/blog/web-security/same-site-cookie-attribute-prevent-cross-site-request-forgery/) prevents CSRF attacks. I choose to use a more relaxed version of this directive (`Lax`) it should be enough in most cases (read about instruction and see yourself if it is enough for you or not).

```
set-cookie: SID=...; SameSite=Lax
```

## Security for assets

All of those HTTP headers are for `m.example.com` and `www.example.com`.


### [`Strict-Transport-Security`](https://helmetjs.github.io/docs/hsts/)

```
Strict-Transport-Security: max-age=86400

```

### [`X-Content-Type-Options`](https://helmetjs.github.io/docs/dont-sniff-mimetype/)

```
X-Content-Type-Options: nosniff
```

### [`X-Frame-Options`](https://helmetjs.github.io/docs/frameguard/)

```
X-Frame-Options: DENY
```

### [`X-XSS-Protection`](https://helmetjs.github.io/docs/xss-filter/)

```
X-XSS-Protection: 1; mode=block
```

### [`Content-Security-Policy`](https://helmetjs.github.io/docs/csp/)

I don't use `Content-Security-Policy` in this post, but I strongly recommend you to use it. (Maybe I will write a separate post about it)

## Security for API

### CORS

Use [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Specify what methods are allowed, and for how long to cache [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)

```
access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
access-control-max-age: 86400
```

Specify from which domain is allowed to access API

```
access-control-allow-origin: https://m.example.com
```

Specify `allow-credentials` otherwise, cookies won't work. Take into account that you can't use the star (`*`)  with credentials directive.

```
access-control-allow-credentials: true
```

### JSON API

For all requests, except maybe endpoints accessible without authentication, require `Content-Type`, this will trigger a check of CORS (via preflight request):

```
Content-Type: application/json; charset=utf-8
```

## JS client

Now we have all the basics, it's time to actually make a call from our frontend to API. Let's use [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for this.

### Anonymous requests

For endpoints which allow access from anonymous users use "plain" fetch. Don't use `Content-Type`, otherwise, it will become slower without any benefit for the user.

```js
fetch(url)
```

### Authenticated requests

For other requests use `credentials: "include"` to enable cookies (this is the default option in the latest Fetch specification, but not all browsers implemented it). Use `headers: { "Content-Type": "application/json; charset=utf-8"}` to trigger CORS check and actually pass check of the backend (which we "implemented" earlier).

For `GET` requests:

```js
fetch(url, {
  credentials: "include",
  headers: { "Content-Type": "application/json; charset=utf-8"}
})
```

For `POST` requests:

```js
fetch(url, {
  credentials: "include",
  headers: { "Content-Type": "application/json; charset=utf-8"},
  method: "POST",
  body: JSON.stringify(params)
})
```

> Photo by Tianshu Liu on Unsplash