Increasingly, we rely on digital devices to manage our shopping, banking, and overall communications. It goes without saying that it’s our responsibility as designers and front end developers to help protect customers from scams and security issues, just as much as it is for backend developers.
Whether you’re developing an ecommerce site (and Shopify looks after the vast majority of the risk) or a different kind of online experience, there are many pitfalls you need to be aware of. For this article we interviewed seven security experts to find out about the most common front end vulnerabilities, and what you can do to mitigate risks and avoid getting hacked.
1. Security must be part of the development process
Lately, there’s been a lot of buzz about front end performance in the community. It made software engineer Benedek Gagyi realize how similar it is to security.
“I keep nodding when I hear statements like ‘you have to add it to your process as early as possible’, or ‘the strongest force opposing your efforts is developer convenience’, since they are equally true both for performance and security,” he explains. “It’s obviously possible to fix all the security-related bugs and holes later in the development lifecycle, but it’s considerably harder and more expensive. That’s why adding threat modelling sessions and regular security reviews is vital to any bigger development step, entailing that security is there by design, not just as a patch.”
Benedek points out that while awareness is important, there should be more talk about the actual developer experience.
“I expect to see far fewer security bugs in software that’s written using libraries and frameworks that make writing secure software easy. It’s trivial, right? A good example in the front end world is how the big frameworks let you know if you are opening yourself up for a cross-site scripting (XSS) attack by giving risky operations names like dangerouslySetInnerHTML
in React or the bypassSecurityTrust
APIs in Angular.”
2. Use a modern framework that handles security automatically
JavaScript frameworks have become an essential part of modern web development. Most sites now seem to be built around a framework like React, Vue, or Angular. From a security point of view, they offer significant benefits.
“The reincarnation of the Angular framework is a perfect example,” says Philippe De Ryck, founder and secure coding instructor at Pragmatic Web Security. “Angular automatically protects against a variety of XSS attack vectors. It offers automatic encoding for simple outputs through {{}}. When using innerHTML
, Angular automatically sanitizes the output. When using variable URLs or CSS, Angular also automatically ensures the values are safe to be used in this context.”
Other frameworks offer similar protections, but according to Philippe they’re not as extensive. Still, using any modern framework significantly reduces the risks a developer needs to be aware of to mitigate XSS attacks.
You might also like: Web Security Fundamentals: What Every Developer Should Know.
3. Avoid typical XSS mistakes
While much less common when using modern JavaScript frameworks, it’s still possible to code in inadvertent XSS flaws into your front end.
“Let’s say we wanted to address a user by their name by linking to them from a marketing email,” James Hall, director of digital innovation agency Parallax, suggests. “Adding ?name=James
to the query string, and then simply adding that to the DOM, would be a quick way to do it.”
For example:
document.querySelector('.tagline').innerHTML = nameFromQueryString
James warns that using code like the above, however, means anyone can inject code into your site and take over. He cautions that just by changing the name to <script src="my.malicious.site">
, an attacker could craft a URL that can make a fake payment page look like it’s serving from your SSL-encrypted website.
4. Consider Trusted Types
Even with countermeasures such as output encoding or sanitization, XSS attacks are still a major problem for web-facing applications. Modern front end frameworks such as Angular or React are not entirely immune to it, warns Liran Tal, developer advocate at open source security firm Snyk and member of the Node.js Security Working Group.
Liran recommends Trusted Types, a new browser API championed by Google’s security folks Krzysztof Kotowicz and Mike Samuel, to address XSS issues by leveraging the Content Security Policy specification (see below, under 12) to define templates of data sources that are used with sensitive APIs such as innerHTML
-like sinks.
The Trusted Types spec still needs to mature but Liran calls for developers to opt in and start using this secure API.
“Luckily, we’re seeing signs of recognition and adoption in the front end community,” he says. “For example, the React framework recently merged a pull request to further extend support for Trusted Types in newer releases.”
You might also like: An Insider's Look at the Technology That Powers Shopify.
5. Consider using textContent
instead of innerHTML
To prevent XSS attacks, you can use a sanitization library like DOMPurify (see below, under 11), but front end consultant Zell Liew suggests that, if you’re changing text only, you can use textContent
instead of innerHTML
.
“Let’s say you want to get a text value from an input field, and you want to output that text value into the DOM. Here’s the code to get the text value:
const value = input.value
But the user can try to enter something malicious, like this snippet:
<img src="x" onerror=alert("HACKED!")>
If you use innerHTML
, you’ll create the <img>
element and run the onerror
handler. This is where XSS begins.”
Zell recommends using textContent
instead, as it can only output text and doesn’t generate any HTML.
“If you don’t generate HTML, there’s no way to insert JavaScript,” Zell explains. You’ll see <img src="x" onerror=alert("HACKED!")>
in the DOM, but the JavaScript won’t run.
6. Compartmentalize your application
Web applications are often built as single applications, deployed in a single origin within the browser. For example, an app running in https://app.example.com
offers public parts, authenticated parts, and even administrator features. However, Philippe De Ryck warns that a successful attack against the public part of the application automatically affects the other parts as well, potentially causing significant damage.
“In the backend, monolithic applications are often split up into smaller components, each running individually,” he explains. “We can apply a similar pattern to front end applications. For example, we could separate the front end application into a public part, an authenticated part, and an admin part. By deploying each part in a separate origin—for example, https://public.example.com
, https://users.example.com
, and https://admin.example.com
—we can ensure that the browser isolates these applications from each other.”
Compartmentalization is key to reducing the impact of client-side vulnerabilities, Philippe believes. “Proper compartmentalization would prevent an XSS vulnerability in the public part of the application from automatically compromising the user information as well.”
You might also like: Deconstructing the Monolith: Designing Software that Maximizes Developer Productivity.
7. Be careful when using Google Tag Manager
Using Google Tag Manager makes it very easy to add the latest tracking scripts, that chatbot the support team wanted, and Hotjar for user analytics.
But James Hall points out that while it’s tempting to allow everyone in an organization (and sometimes outside) access to your Google Tag Manager, you need to be careful.
“If a Google account gets hacked, it’s possible to add any arbitrary JavaScript to your website,” he warns. “A sophisticated attack could take your users off to a fake payment page for them to complete their order, sending money to someone else!”
8. Be more selective with third-party scripts
Even if access to your Google Tag Manager isn’t compromised, the tracking scripts you choose to add could themselves get hacked. The use of third-party libraries and open source components in JavaScript-based web applications is a common practice, but it opens you up to vulnerabilities you can only control to a degree.
“If you add the ‘flavor-of-the-week’ chat widget to your site, anyone gaining access to their servers can now modify your website,” James Hall cautions. “This happened to British Airways and Ticketmaster when they included a push notification library called Feedify.”
James advises that being more selective about which third parties have access to your pages will also help you comply with the EU’s General Data Protection Regulation (GDPR). The less information you’re giving away, the less you’ll need to make people aware of in your privacy policy, which means there’s a lower chance of violating GDPR.
Instead of having other third parties host your JavaScript framework, James also recommends considering getting Shopify to host it for you.
“They’ll do a better job at keeping you safe from any nasties than automatically hotlinking the latest version of an open-source dependency,” he suggests.
You might also like: What App Developers Need to Know About GDPR.
9. Audit your dependencies
You likely use many local build tools to create your front end, like SCSS plugins, for example. James Hall advises making sure you periodically run npm audit
to show a list of vulnerable packages and upgrade them to avoid including security issues into your built JavaScript files inadvertently.
If you use GitHub, it will now flag vulnerable dependencies, and there are also alternative services like Snyk that can check your source code automatically and open pull requests to bump versions.
Finally, if you’re building a theme for a client without Google Tag Manager access, James recommends performing a quick audit with a tool like BuiltWith.
10. Use Subresource Integrity for third-party CDN hosting
Sometimes you can’t avoid third parties, however. Snyk’s Liran Tal points out that we often use externally-hosted libraries for fonts and CSS and import them to web apps with a content-delivery network (CDN).
“What would happen if someone were to gain access to the code of these libraries and replaced them with their own malicious version?,” he asks. “What would happen if the network medium was compromised or found to be insecure? Our own source code could even get compromised by attackers gaining access to a build server or to the cache or storage of our own code that’s being delivered from a CDN!”
To help solve this problem, Liran suggests Subresource Integrity, a spec that provides proof of integrity of the resource being used in a web page and conveys that its content hasn’t been tampered with. Resources are declared using an integrity attribute that makes use of a cryptographic hash that the browser validates before making a functional use of the resource.
James Hall agrees and says Subresource Integrity is great for ensuring that the asset is identical to the one that you intended to include. This is a checksum that makes sure your script is the same as it was:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js"integrity="sha384-NaWTHo/8YCBYJ59830LTz/P4aQZK1sS0SneOgAvhsIl3zBu8r9RevNg5lHCHAuQ/" crossorigin="anonymous"></script>
11. HTML encoding is not enough
Ilya Verbitskiy, co-founder of WebStoating, an agency helping companies to create a successful online business, recommends paying special attention to HTML encoding.
“It works well if a user’s input is placed within an HTML tag, for example <div>
or <p>
. It also works for the data that is used within HTML attributes. But HTML encoding won’t help you when you are rendering a user’s input inside a <script>
tag, or in a URL, or within an event handler like onclick
or onerror
.”
Ilya points out that, for example, the following snippet is dangerous:
<a href="https://example.com" onmouseover="@Model.UserInput">Sample Link</a>
It is dangerous because Model.UserInput
might be alert(document.location)
or:
eval(String.fromCharCode(105,102,40,33,119,105,110,100,111,119,46,120,41,123,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,108,111,99,97,116,105,111,110,41,59,119,105,110,100,111,119,46,120,61,49,59,125))
Another dangerous code might look like the following:
<a href="@Model.UserLink">Sample Link</a>
In this case, Model.UserLink
might be:
javascript:eval(String.fromCharCode(105,102,40,33,119,105,110,100,111,119,46,120,41,123,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,108,111,99,97,116,105,111,110,41,59,119,105,110,100,111,119,46,120,61,49,59,125))
This will cause trouble for users since the script will be executed once the user will click it.
“Writing all the encoders to prevent XSS is not an easy task,” Ilya admits. “First, the library should support different execution contexts. It must understand different encoding supported by browsers and be aware of potentially dangerous HTML attributes that must be filtered out.”
Ilya recommends using libraries that have already implemented recommended XSS protection techniques and are freely available:
- DOMPurify: Simple to use, as it has only one method to sanitize the user's input. Its default policy has been proven by multiple users, comes with the option to customize the rules, and supports HTML5, SVG, and MathML.
- secure-filters: This library from Salesforce provides methods to sanitize HTML, JavaScript, inline CSS styles, and other contexts. It’s especially useful if you want to make use of user input in other places, for example generating CSS or JavaScript.
12. Implement Content Security Policy
‘Layered security’ or ‘layered defence’ are well known approaches in the cybersecurity space and describe the practice of combining multiple security controls to protect data. Ilya Verbitskiy says it’s applicable to front end security as well. “Even if the site was compromised via a XSS vulnerability, we must minimize user damage,” he advises.
“The usual result of an XSS attack is that scripts are being injected into a vulnerable webpage,” Ilya explains. “The script either steals a user’s cookies and data from local and session storages, injecting a keylogger or even doing cryptocurrency mining. Most attacks require a channel to communicate between the hacker’s server and a victim. The communication channel might be an AJAX call, an <img>
tag, an iframe, or a web-socket. Your aim should be to break the communication channel.”
All modern browsers have already implemented a solution: Content Security Policy (CSP), an added layer of security that helps to detect and mitigate certain types of attacks, including XSS and data injection attacks. To enable CSP, you need to configure your web server to return the Content-Security-Policy
HTTP header that helps you mitigate XSS risks by declaring which resources are allowed to load. For example:
Content-Security-Policy: default-src 'self'; child-src child-src 'none'; img-src https://cdn.example.com 'self'; object-src 'none'
If your website URL is https://example.com
, CSP blocks usage of the <embed>
, <object>
, and <applet>
tags, as well as frames and web workers. The browser will only load images from https://example.com
and https://cdn.example.com
. All other resources, for example, scripts and stylesheets, have to be hosted on https://example.com
.
Alternatively, you can define a policy using the <meta>
tag within your HTML page:
<meta http-equiv="Content-Security-Policy" content=" default-src 'self'; child-src child-src 'none'; img-src https://cdn.example.com 'self'; object-src 'none' ">
Content Security Policy provides a lot of directives to help you define the policy that works best for your project. Some of the most widely used directives include default-src, child-src, script-src, style-src, img-src, connect-src, etc. Ilya recommends making sure you always define default-src as a fallback in case you forgot a directive.
You might also like: Mobile Design Trends in 2019.
13. Be mindful of what you’re exposing
Finally, be really mindful of the data you’re exposing to the front end in your code.
“Making use of product JSON on the product page may be beneficial in some cases, but it can also mean you’re displaying what should be private merchant data to the frontend,” cautions Kelly Vaughn, founder of The Taproom Agency. “A good practice is to store this sort of data in a metafield, as each unique metafield has to be manually given permission to be readable via the API.”
And if you’re using source control for theme development such as GitHub, always keep your API keys hidden, Kelly recommends. Making your config.yml or .env file available on GitHub gives anyone access to your store’s theme (and any additional permissions you allowed via the private app). Also only allow access to the features you truly need for an app. If you’re using a private app just to build or make changes to a theme, you should disallow all access except for read/write themes.
Security fosters trust
Security is more important than ever before. Breaches can happen to anyone, whether it’s a large corporation or a small site. We need to be careful not to expose customer data, and we need to be aware of the many attacks that are possible and the errors that can happen when deploying a website or app. Modern JavaScript frameworks take care of a lot of the risk, but they can’t be relied upon entirely.
Customers expect their online experience to be safe and that any information they provide will not be stolen or used in ways they didn’t expect. Clients in turn expect you to protect their sites, their data, and their customers. Follow the various ways you can secure sites yourself and reduce vulnerabilities that we explored in this article and you will foster trust and show your client that you take security seriously.
Read more
- 4 Easy Ways to Deploy Your Website or App
- 10 Ways to Create Delightful and User-Friendly Web Animation
- How to Refactor a Shopify Site for Javascript Performance
- Free Webinar] Developing with Shopify: Using JavaScript to Add Ecommerce to Any Website
- How Open Graph Tags can Optimize Social Media Experiences
- A Modern Approach to CSS Pt. 2: Building Resources
- Add Products Faster With Product CSVs
- 5 Easy-to-Implement Website Maintenance Tips for Your Client's Ecommerce Business
How do you prioritize security? Share your thoughts in the comments below.