TL;DR → Don’t expose API keys in frontend. Instead, use serverless functions (Cloudflare Workers) to safely handle user feedback and add a layer of security against bad actors.
So... I Revisited My Portfolio
You know that feeling when you revisit your old projects and wonder what you were thinking? Yeah, I had that moment while reviewing my portfolio site (React + GitHub = Professional Portfolio Website)
Upon reviewing my existing work, I discovered a glaring security risk! I injected Firebase API keys into the react app to push user feedback directly into Firestore... with no read and write restrictions.
This meant anyone can read/ write to my FireStore without restriction. Moreover, I wasn’t really notified when new feedback was submitted, so I missed out on important messages.
My goals were to make following improvements:
- Remove security defect and receive notifications.
- The new solution must be low cost/ free since this is a hobby project.
Let's get to work 👷♂️🏗️
Remove the vulnerable code
I ripped out the Contact me
section. That's where I initialized Firebase app instance and exposed API keys via good old REACT_APP
route to a vulnerable Firestore instance. I also deleted the firestore project so the credentials embedded into my code cannot be misused.
Key learning: Never inject sensitive data into frontend code
Explore options for Serverless functions
To securely handle user feedback without exposing API keys, I explored serverless functions.
Since this is a hobby project, I wanted a free solution. I started exploring different options and stumbled upon CloudFlare Workers which offers up to 1000,000 invocations per day source.
Honestly, if my portfolio form ever reaches that many feedback or collaboration requests, I’d consider it a great problem to have — and only then would I think about paying for extra capacity. 💵💵
**Or if a bad actor decides to run a Denial of Service attack on my function then I'll use up my daily request limit and will have to wait for the limit to reset.
Workflow
So, the game plan is, the Contact me
form submission invokes a serverless function that receives request, verifies the data and perform the required action. Thus, the sensitive keys remain on the server-side only, not visible to the user, making them much harder to compromise.
Cloudflare Worker Setup
You can also checkout my code for contact-worker: Contact Worker Source Code
For testing, I followed this article to perform behavioural tests on my function locally before I could really deploy it for the world to use: Behavioural tests with vitest
To deploy the worker, I used GitHub workflows and passed the required keys via secrets.
You can checkout my workflow file: GitHub Workflow
With the backend ready and deployed, you can modify the Frontend to send requests to the function.
⚠️ Ensure your serverless function handles preflight requests. Without this, CORS may block requests from the browser. You can check how I handled it here: CORS Support
Pass all the credentials via Dashboard as secret
Go to Cloudflare Dashboard > Compute Workers > Workers and Pages > Select the worker you deployed > Settings > Variables and Secrets
Define all the secrets here.
The benefit of defining secrets instead of environment variables is it doesn't get overwritten when worker is redeployed via GitHub workflow. The wrangler.toml file doesn't take precedence.
Setup Slack bot and Channel
To relay message from worker to slack, we will need a slack bot. There are tons of tutorials and videos online about the setup. Here is the one I followed: Send Message to Slack with a Node.js Script
Again, you can reference my worker implementation to understand how I enabled it.
Result
Front end contact form
The submission process remains the same. The difference lies in how the request from frontend is handled and responded to.
Message received in Slack channel
Benefits of this approach
- With Cloudflare workers, I have increased visibility into where the requests are originating from so I can promptly respond to bad actors and block them if required.
- I have more control over kind of data I accept, any malicious data can be stopped at worker before relaying it to Slack as a message.
- Credentials are stored as secrets so bad actors will have a hard-time deducing them.
Future improvements
- Rate limiting is a paid feature provided by Cloudflare so I will need added protection against DDOS attacks. I will come up with my own rate limiting design to handle such cases (if they occur).
Hope this article gave you some insights into security practice and technologies used!
Feel free to comment below if you have more ideas on improving my strategy and feedback 😊😊
Top comments (0)