Skip to content

JS security

Best practices for JS security

1. Check regularly for npm package security issues

npm audit // shows vulnerable packages
npm audit fix (--force) // fixes vulnerabilities
npx npm-check-updates // checks outdated packages
npx npm-check-updates -u // automatically fixes outdated packages (careful for breaking changes)

2. Implement eslint security rules

eslint-plugin-security

3. Use Best Practices

4. JWT vs servers-side sessions

JWTs provide a means of maintaining session state on the client in stead of doing it on the server.
With server-side sessions you will either have to store the session identifier in a database, or else keep it in memory and make sure that the client always hits the same server. Both of these have drawbacks. In the case of the database (or other centralised storage), this becomes a bottleneck and a thing to maintain – essentially an extra query to be done with every request.
With an in-memory solution you limit your horizontal scaling, and sessions will be affected by network issues (clients roaming between Wifi and mobile data, servers rebooting, etc)
Moving the session to the client means that you remove the dependency on a server-side session, but it imposes its own set of challenges.- Storing the token securely- transporting it securely- JWTs Sessions can sometimes be hard to invalidate.- Trusting the client’s claim.
These issues are shared by JWTs and other client-side session mechanisms alike.
JWT in particular addresses the last of these. It may help to understand what a JWT is:
It is a bit of information. For user sessions you could include the username and the time when the token expires. But it could conceivably be anything, even the session ID or the user’s entire profile. (Please don’t do that though) It has got a secure signature that prevents malicious parties from generating fake tokens (You need access to the server’s private key to sign them and you can verify that they were not modified after they were signed) You send them with every request, just like a cookie or Authorization Header would be sent. In fact they are commonly sent in the HTTP Authorization header but using a cookie is fine too.
The token is signed and so the server can verify its origin. We will assume that the server trusts its own ability to sign securely (you should use a standard library: don’t try to do it yourself, and secure the server properly)
On the issue with securely transporting the token the answer is commonly to send it via an encrypted channel, usually httpS.
Regarding securely storing the token in the client, you need to ensure that the bad guys can’t get to it. This (mostly) means preventing JS from bad web sites from reading the token to send it back to them. This is mitigated using the same strategies used to mitigate other kinds of XSS attacks.
If you have a need to invalidate JWTs there are definitely ways this can be achieved. Storing a per-user epoch for only users who have requested to have their “other sessions terminated” is a very efficient method that will probably be good enough. If a application needs per-session invalidation, then a session ID can be maintained in the same way and the “killed tokens” table can still be maintained to be much smaller than the full user table (You only need to retain records newer than the longest allowed token lifetime.) So the ability to invalidate the token partially negates the benefit of client-side sessions in that you would have to maintain this session killed state. This will more than likely be a much smaller table than the original session state table, so the lookups are still more efficient though.
One other benefit of using JWT tokens is that it is reasonably easy to implement using libraries available in probably every language you can expect to have it. It is also completely divorced from your initial user authentication scheme – if you move to a finger print based system you do not need to make any changes to the session management scheme.
A more subtle benefit: Because the JWT can carry “information” and this can be accessed by the client you can now start do some smart things. For example remind the user that their session will be expiring a few days before they are logged out, giving them the option to re-authenticate, based on the expiry date in the token. Whatever you can imagine.
So in short: JWTs answers some of the questions and shortcomings of other session techniques.1. “Cheaper” authentication because you can eliminate a DB round trip (or at least have a much smaller table to query!), which in turns enable horizontal scalability.2. Tamper-proof client-side claims.
While JWTs does not answer the other issues like secure storage or transport, it does not introduce any new security issues.
A lot of negativity exists around JWTs but if you implement the same security that you would for other types of authentication, you will be fine.
One final note: It is also not Cookies vs Tokens. Cookies is a mechanism for storing and transporting bits of information and can be used to store and transport JWT tokens too.

A few years ago, before the JWT revolution, a <token> was just a string with no intrinsic meaning, e.g. 2pWS6RQmdZpE0TQ93X. That token was then looked-up in a database, which held the claims for that token. The downside of this approach is that DB access (or a cache) is required everytime the token is used.

JWTs encode and verify (via signing) their own claims. This allows folks to issue short-lived JWTs that are stateless (read: self-contained, don’t depend on anybody else). They do not need to hit the DB. This reduces DB load and simplifies application architecture because only the service that issues the JWTs needs to worry about hitting the DB/persistence layer (the refresh_token you’ve probably come across).

5. Cookies

Cookies that have httpOnly (prevents against XSS attacks), secure and sameSite set to true are more secure.

JWT is simply a token format. A cookie is an HTTP state management mechanism really. A web cookie can contain JWT and can be stored within your browser’s Cookies storage.

5a Session-bases authentication vs token-based

OKEN-BASEDSESSION-BASED
StatelessStateful
The authentication state is NOT stored anywhere on the server-sideThe authentication state is stored on the server side (DB)
Easier to scale horizontallyHarder to scale horizontally
Commonly uses JWT for authenticationCommonly uses Session ID
Typically sent to the server via an HTTP Request Authorization Header (e.g. Bearer <token>). Can use Cookie tooUsually sent to the server in the Cookie request header
Harder to revoke a user sessionAble to revoke user session with ease

5b. Cookie vs Bearer Tokens : https://jerrynsh.com/all-to-know-about-auth-and-cookies/

6. Prevent against CSRF attacks with CORS: control allow origin

Cookies are vulnerable to CSRF attacks. No cookies = no CSRF attacks. Browsers automatically send cookies (no client-side code needed) along with every request via the cookie request header. This is exactly why Cookie (storage) is vulnerable to CSRF attacks.

terminal
 res.set('Access-Control-Allow-Origin', '*');

7. Web storage

= vulnerable to XSS attacks

sessionStorage — data is persisted only for the duration of the page session
localStorage — data is persisted even when the browser is closed and reopened

 LOCAL/SESSION STORAGE
JavaScriptAccessible through JavaScript on the same domainCookies, when used with the HttpOnly cookie flag, are not accessible through JavaScript
XSS attacksVulnerable to XSS attacksImmune to XSS (with HttpOnly flag)
CSRF attacksImmune to CSRF attacksVulnerable to CSRF attacks
MitigationDo not store private/sensitive/authentication-related data hereMake use of CSRF tokens or other prevention methods

8. Prevent against XSS (Cross site scripting)  attacks:

= Basic XSS attacks attempt to inject JavaScript through form inputs, where the attacker puts an alert(localStorage.getItem('your-secret-token')) into a form to see if it is run by the browser and can be viewed by other users.

React library performs escaping logic components via the render method.  Only worry about excaping in following cases:

  • Do not use dangerouslySetinnerHtml and if you do, escape the values
  • Props are not validated, sanitized, or contextually escaped when they are passed to React.createElement; they are later added to the DOM by ReactDOM.render as attributes on elements, rather than text content. Some HMTL attributes allow you to inline javascript protocol-based links that execute JavaScript in the context of the current page. These handlers will be added to the page if you don’t handle them with custom code in your React components.
  • If you are styling your React Components using CSS-in-JS libraries like styled-components and glamorous, then ReactDOM.render does not escape the values you pass into the CSS. Attackers who can control the value of CSS properties passed in as props can inject malicious CSS and install key loggers and other tools in your users’ browsers.

9. Install security packages:

  • helmet
    What it does: Sets security-related HTTP response headers to protect against some well-known web vulnerabilities.

    What does it protect against: Cross-site scripting attacks, cross-site injections, clickjacking, MIME sniffing and targeted attacks towards Express servers by disabling the X-Powered-By header.

const app = require('express')();
const helmet = require('helmet');

// Using helmet middleware
app.use(helmet());
  • xss-clean

    What it does: Sanitizes user input coming from POST request body (req.body), GET request query (req.query) and URL parameters (req.params)

  • What does it protect against: Cross-site scripting / XSS attacks

const app = require('express')();
const xssClean = require('xss-clean');

// Protect against XSS attacks, should come before any routes
app.use(xssClean());
  • hpp

    What it does: Puts the array parameters in req.query and/or req.body asides and just selects the last parameter value to avoid HTTP Parameter Pollution attacks

  • What does it protect against: Bypassing input validations and denial of service (DoS) attacks by uncaught TypeError in async code, leading to server crash

const app = require('express')();
const hpp = require('hpp');

// Protect against HPP, should come before any routes
app.use(hpp());
  • express rate limit

    What does it do: Used to limit IP addresses from making repeated requests to API endpoints. An example would be to rate limit an endpoint that is responsible for sending password reset emails, which can incur additional fees

  • What does it protect against: Brute force, denial of service (DoS) and distributed denial of service (DDoS) attacks.

const app = require('express')();
const rateLimit = require('express-rate-limit');

// Restrict all routes to only 100 requests per IP address every 1o minutes
const limiter = rateLimit({
    windowMs: 10 * 60 * 1000,    // 10 minutes
    max: 100                     // 100 requests per IP
});
app.use(limiter);

Useful links:
Best practices security JS
Security best practices
ciper suites

Published inJS

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *