Securing your Single-Page application Anno 2019
First we had cookies, then we had tokens and now it looks like we’re back to cookies.
Let’s take a closer look to the options and zoom in on the relevant parts.
The major options for giving a user access to our web-based resources are:
- Cookie based authentication
- Token based authorization / authentication
- Federated / delegated authentication (SSO)
- Windows authentication (Kerberos / NTLM)
Cookies and tokens will be explained further down the post. Along the road we will also talk about federated or delegated authentication in Single Sign-On (SSO). Because I have a mixed feeling about Windows authentication, let me just pretend it doesn’t exist. One thing though: if you can avoid it, please do, consider Azure AD or AD FS as an alternative.
Cookie based authentication
The information inside the cookie is encrypted by a secret only known by the server.
Session identifier based cookie
In this case the cookie only contains a unique identifier that refers to a user or account record in the database. For each request the browser makes to the server, the server will lookup the user identity based on the identifier in the cookie.
Claim based cookie
A claim based cookie contains all the claims the server knows about the authenticated user. This way, for each request the browser makes to the server, no database lookup is needed as the user identity can be reconstructed from the information in the cookie.
Just like with stateless tokens, you need some kind of way to replace the cookie in case one of the claims changes. With cookies this could be done on every request as the server can decide when to replace the cookie.
- Need to set XMLHttpRequest.withCredentials to true otherwise no cookie will be sent to the server
- Watch out for the 4KB cookie limit per domain, especially when using claim based cookies (see how big can a web cookie be)
- Needs protection against Cross-Site Request Forgery (CSRF)
Token based authentication
The older SAML 2.0 and WS-Federation protocols are token based but are only suitable in federated login scenario’s: the token is only used once as proof of identity at which time it gets exchanged by a different authentication scheme.
The more popular token based protocols like OAuth2 and OpenID Connect are different in that regard as their token can be reused multiple times to access protected API resources. This said, OAuth2 and OpenID Connect also allow us to only use them as federated login (think about the login with Facebook or Google options on many third-party sites).
OAuth2 is an industry standard protocol for authorization. It is built for web applications, desktop applications, mobile phones and living room devices.
OAuth2 supports multiple grant types but only the implicit grant was deemed suitable for browser-based SPAs. It works by sending a short-lived access token to the SPA which authorizes the SPA to access protected API resources, mostly on behalf of the logged-in user (as most systems link an actual user to the access token).
Just like with session cookies, a server-side lookup is needed on every request in order to identity the user based on the access token.
OpenID Connect is an identity layer on top of OAuth2. Many external login providers doing OAuth2, like Facebook, Google, LinkedIn, etc already provided a way to exchange their access token for additional user information by calling some kind of user-info endpoint which was very specific to the provider.
OpenID Connect has standardized the user-info endpoint and also makes it possible to receive an id token (JWT) in addition to the access token we know from OAuth2. Apart from the user-info endpoint, it also standardized scopes, endpoint discovery, dynamic client registration and sessions management.
OAuth2 allows us to use an API, OpenID Connect allows us to authenticate a user
What’s wrong with tokens in SPAs?
Not as secure as promised
- Token leaking is real. Because tokens are sent as hash-fragment in the URL, they appear in browser history and eventually in logs
- JWTs have had their own set of issues and stupidities
- OAuth2 and OpenID Connect is vulnerable to Cross-Site Scripting (XSS) which is much harder to circumvent than Cross-Site Request Forgery in the event of cookie based authentication
Not as stateless as promised
The JWT promises us stateless authentication, but in practice you’ll almost always end up doing a lookup for every request. This will be to check if a token has not already been revoked, to verify additional authorization rules, etc.
Static resources like images (<img />-tag) or downloadable content (<a />-tag) are hard to protect with tokens. One options is to include the access token as a query string parameter, causing the token to also end up in the server logs.
Advice of the OAuth2 IETF working group
At the end of 2018, the OAuth2 IETF working group advises against using OAuth2 (and thus also OpenID Connect) for SPAs served from the same domain as the API (which is probably also the case for your SPA).
application is served from the same domain as the API (resource
server) being accessed, it is likely a better decision to avoid using
OAuth entirely, and just use session authentication to communicate
with the API.
OAuth and OpenID Connect provide very little benefit in this
deployment scenario, so it is recommended to reconsider whether you
need OAuth or OpenID Connect at all in this case. Session
authentication has the benefit of having fewer moving parts and fewer
attack vectors. OAuth and OpenID Connect were created primarily for
third-party or federated access to APIs, so may not be the best
solution in a same-domain scenario.
Read the full draft here.
What’s the alternative?
The alternative for the aforementioned scenario is SameSite cookies. When the “SameSite” cookie attribute is set on a cookie, the browser will only send the cookie to the server when both the SPA and the API are on the same domain. Which is an effective protection mechanism against CSRF.
As you can see in the above image, browser support is fairly good. If, for some reason, you also want to protect users with older browsers, the good-old Anti-Forgery token (CSRF token) will do the trick. Luckily, most server-side frameworks make it easy to work with Anti-Forgery tokens as it’s a solution that already exists for decades.
Tokens still have their use
While we shouldn’t use tokens anymore for our SPA, tokens still have their use in other scenario’s:
- Service-to-service calls
- Single Sign-On scenario’s
Apart from all the above, there are a few more thoughts I want to share with you regarding securing your SPA:
- Stop using HTTP, even during development. Use HTTPS.
- CORS Allow all? Of course NOT! Never allow everything in CORS, except you’re creating an extremely public API. During development, you might have to allow one or more addresses when running back-end and front-end separately. In production, since your SPA is on the same domain as the API: disable CORS.
There, that’s all I’ve wanted to say for now. I hope the article is of any use. Feel free to leave a comment if the article has helped you, you want to discuss something or you just want to point out a major mistake.
Have a nice day!