Starting my web security series by sharing my secrets - just kidding! Let's talk about keeping our secrets safe.
Environment Variables
Environment variables are like global server variables. They're often used for secrets and sensitive information. There are ways to expose them to front-end code in the browser, but then they're not secrets anymore. Anyone can find them.
There are packages like dotenv that allow you to configure environment variables in files (just remember to put the files in your .gitignore
). You can configure them when you host a site using a platform like Heroku or Netlify.
You can also set them in your terminal environment manually or in files your terminal uses.
Another common use case for environment variables is for information that will change by environment. For example, you'd have an environment variable denoting the environment like NODE_ENV=testing
or NODE_ENV=production
. Then you'd have code like:
if (NODE_ENV=testing) {
URL_BASE=http://localhost:3000
} else {
URL_BASE=http://mydomain.net
}
Depending on how you deploy, it's also possible to have separate projects for each environment, and just change the values of the variables.
Secret Servers
You may be asked to use a secret server to store your secrets. Some are set up similar to a password manager - just to share credentials. Others are servers that allow you retrieve secrets with an authenticated HTTP request.
Products like Google Cloud have the authentication, authorization, secret storage, and secret retrieval built into the system you use to deploy your code.
If secrets are stored this way, instead of hardcoded in an environment variable, it's much easier to rotate them frequently.
Tokens
Tokens are a string representing information that has been encrypted, often using a cryptographic hash. The information is for authentication and authorization purposes. When you're a user of an app, they're typically using tokens to authenticate and authorize you. You'll usually get keys, or tokens, from APIs to authenticate and authorize your app as a developer.
There are lots of encryption algorithms, but one you'll commonly come across in web development is a JWT. A JSON Web Token has cryptography features around JSON. In other words, they're designed to be compact, URL-safe, and easily consumed in JavaScript. Perfect for HTTP requests in web development. They're even formatted like an HTTP request with headers, a payload with the JSON data, and a signature. The signature is another type of cryptography, code signing. In short, code signing aims to guarantee you can trust the information by verifying the source.
Like many cryptographic standards, the JWT algorithms are not secret. You can paste a JWT into Auth0's JWT debugger and see the information inside. This is why you should keep JWTs, and all tokens you receive, secret. Rotate them often. Only send them over HTTPS.
As secrets, tokens should stay in the server, but we often need to store information about users in the browser. Let's talk about cookies.
Cookies
After you login and a server has received your authentication/authorization token, it will repackage the information the front-end needs into a cookie and send it via HTTP.
There's a specific set-cookie
header. You can use multiple set-cookie
headers and each takes one key value pair (e.g. a JavaScript object or a Python tuple).
The most secure cookie has the secure
, SameSite
, and HttpOnly
attributes set. The first means it can only be sent over HTTPS. The second prevents CORS requests from accessing the cookie if it's set to strict
. The lax
value sends the cookie when a user navigates to the same origin. The value none
doesn't restrict requests for the cookie.
`HttpOnly` only allows the cookie to be read by HTTP request. You won't be able to access it with the cookie API. In other words, if you don't set HttpOnly
and SameSite=strict
, anyone can access your cookie in the browser with JavaScript.
If you need to store session information about a user in the front-end, you don't have to put anything secret in your cookie. Instead, the front-end will store a token or id that expires, essentially a public key. It doesn't matter if someone knows the public key if the private key is stored on the back end for verification. Only when the two are put together is sensitive information revealed.
Man in the Middle
Why should you guard your tokens and cookies so closely? Man in the Middle attacks. They go by many names, and monster in the middle is my personal favorite.
Using HTTP as an example, the monster intercepts the information in the middle, between the sender and the recipient. Then, they'll repackage the information like they never opened it and send it on its way. Sometimes they want the secrets in your cookies. Other times they're more interested in how you're code signing or encoding the information.
Check out the OWASP article for more information.
Web Storage API
If you're already planning on storing non-secret information in the front-end, you can also use the Web Storage API.
The Web Storage API has two parts - session storage and local storage. Session storage only stores things for a session - as soon as you close the browser or tab, it's gone. Local storage has no expiration. Both store key value pairs.
There are built in methods, so storing and accessing information in Web Storage is as easy as:
localStorage.setItem(key, value)
sessionStorage.getItem(key)
IndexedDB API
The IndexedDB API is like the Web Storage API, but bigger. It's a whole object-oriented database, right in the browser. This means you can store a lot of not secret information.
It's persistent storage and works online and offline. For more information, check out Mozilla Developer Network's guide.
Session Hijacking
Why am I so adamant you shouldn't store secrets in the browser? Session hijacking.
There are multiple types, including a man in the middle attack. A common one is to gain access to session information stored in the browser. The goal is either to send it to the server and gain access there or maintain your session, impersonating you.
Attackers can use trojan horses, clickjacking/UI redressing, and XSS to session hijack as well. Always assume anything stored in the browser is compromised.
Conclusion
This advice comes from experience. I've exposed keys to the browser, not realizing how dangerous that was. My husband has been the victim of a session hijack.
I thought I'd get started with a general overview of secrets and where not to store them. Web security boils down to making it so time consuming that it's not worth it and assuming your secrets are going to be compromised. Tokens, with their encoding algorithms and recommended frequent rotation, are a great example of that.
I'll talk more about server, HTTP, JavaScript and HTML vulnerabilities in other blogs in this series.