Closed mbodeantor closed 1 year ago
I updated the Vue app with the API key and changing the "county" field to "location". As for protecting the API key from being exposed in the browser, I'm not too sure how to fix that. We could have each user sign up and login to create their own API key, but I think that would create a barrier for people who just want to use the quick search feature. I wonder if we could just not require an API key for some of the routes that are just reading the database (ie quick search) and only use the API key for protecting the database from being written/updated on.
@josh-chamberlain Do you know of a way to hide the API key in the request on the browser side?
@kalenluciano @mbodeantor our route for people to use the data sources quick search will be through the app—the home page of app.pdap.io will be our quick search page, for which people don't need to use the API!
Anyone who wants to use our API should need to authenticate and use our JWT system, sending that in each the request.
When you ask about hiding the API key in the request—do you mean the browser's API key when it's using the front end?
@josh-chamberlain Yes, when using the front end you can see the API key in the request it is making to the API in the browser
@mbodeantor Do environment variables in DigitalOcean not handle this? I thought their app platform functioned like middleware, so it's using our envars to prevent them from ever being in the browser.
@josh-chamberlain It prevents us from storing them in Github, but it gets injected when passing the request to the API. It may not be an issue, I'm guessing the average user won't know to look.
We attract enough privacy and hacker-y types that I think we should have a professional and secure way of handling this...otherwise, what's the point of secret API keys? Researching on the internet and GPT tells me this should be avoidable...some synthesized advice:
Backend Handling: Ensure that when the backend communicates with the external API, it adds the "Authorization" header (from environment variables or secrets) just for that outgoing request. When relaying information back to the frontend, your backend should NOT include this header or its value. This keeps the token or key confined to server-to-server communication and invisible to the client.
Frontend: If the frontend is directly making any calls to your backend and you're seeing this "Authorization" header, it means the frontend is somehow getting a hold of these headers from the backend's response or some misconfiguration. Ensure that frontend code doesn't have any logic to display or process headers from the backend's response, especially the "Authorization" header.
Backend Response Sanitization: Before sending a response back to the frontend, sanitize the response headers. This means stripping out any headers not meant for the frontend, especially "Authorization" headers.
Any of that ring true?
@kalenluciano looks like this might be a way to address it: https://stackoverflow.com/questions/43242730/vuejs-and-vueresource-removing-header-fields-from-a-ajax-request
I tried following the stackoverflow post, but I ran into a couple issues. The post is a bit old and based on VueJS2 (and we're using VueJS3), so I had to use axios' built-in config interceptor, which seemed to be similar to what VueResource does. However, once I got it set up, I still was able to see the API key in the request. I can push the code for your reference.
To your reply earlier @josh-chamberlain, all requests made through the app, including the quick search functionality, are routed through the Flask API. Currently, we ensure the security of our API and the underlying Data Source database, which is both read from and written to by the API, by enforcing the requirement of a valid API key for every request. This is achieved through the '@api_required' decorator, applied to every route, including quick search, retrieving all data sources, and fetching all agencies.
To my understanding, our goal is to allow anyone to use the quick search functionality, while maintaining restrictions on write or update operations. If this is the case, we could consider removing the API key requirement for quick search requests. Anyone would be able to search the database for information via the quick search, but modifications or write operations would remain restricted with an API key that users would have to register for. This is what I've done in past projects.
@kalenluciano I think we're on mostly the same page, using slightly different terminology. Anyone should indeed be able to use the quick search interface—these users would not be logged in or authenticated, so our front end app would be the authenticated API "user". If we don't require an API key, we won't know who's using which endpoint for what and we're somewhat vulnerable.
Either way, some day soon the front end is going to need to make authenticated POST requests to make changes to data sources, create users, etc. so we can't have it displaying API keys. Are the keys getting bundled with the front end during build? Thoughts on my numbered points above?
Gotcha, that makes sense. The API key isn't getting bundled with the front end during build because we store it as an environment variable. Once we deploy it, we'll have to add it in as a secret variable. As Marty pointed out though, users can still see the API key in the header for each request sent to the API.
To the numbered points above, I agree that we want to keep the API key between server-to-server communication. For this to happen, I think we need to add to our authentication system in the API by encoding the API key into a JWT token that we can decode and verify in our API for each request. This way, the only thing the user sees in the header of their requests is an encrypted version of the API key that only the API can decode with a SECRET key. I can try working on this today and report back.
I just sent another PR (now attached to this issue) that builds out our authentication system using JWT tokens. Previously, we were creating an API key and using that to verify an authenticated user. The problem with this is that the frontend needed to have the API key and send it in the headers of each request, exposing it to users.
This PR fixes the problem by creating a JWT token at log in. The token contains the API key, but it's encoded using a SECRET_KEY that only the API has access to (and is never sent to the frontend). Each request from the frontend will need to send the JWT token to the API. From there, the API will first verify that it's a legitimate JWT token using the SECRET_KEY and then after verifying, the API will extract the API key from the token and use that to interact with the database.
I tested this locally and it worked for me. You'll need to run pip install -r requirements.txt
, include the SECRET_KEY in the API .env file, and include the JWT token in the Vue .env file. Let me know what you both think @josh-chamberlain @mbodeantor
Context
We've made some changes to the API since the app was last updated, it will no longer return results properly
Requirements
Tests
Docs
Open questions