CORS and the Infura API Secret
This guide describes how to allow the Infura API Secret to be used in browser-based JavaScript without causing a CORS error.
Background
When you use your Infura API key to make requests, it is used to identify you to Infura and keep track of your usage.
Here's an example of a basic request:
curl https://mainnet.infura.io/v3/**YOUR-API-KEY** \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}'
For extra security you can toggle the "Require API Key Secret for all requests" option for the relevant API key. You can find this setting behind the "Configure" button next to that API key on the MetaMask Developer Dashboard, and it ensures that a basic authentication header is sent with each request. Read more in the MetaMask docs.
This option is fine for backend code, but for browser-based JavaScript, it leaves the API key secret exposed in the code to anyone willing to do a bit of digging.
In fact, Infura will fail to return the magic CORS headers if an authentication header is sent. The request from the browser then fails with a CORS error.
If your frontend communicates with a nodejs (maybe express) backend then the solution is to make your Infura requests from the backend, where the API secret can be kept secure. But what if your architecture is frontend only?
Solution
The best solution is to implement a proxy server. The front-end app talks to the proxy server, which adds an authentication header to each request. This way the API secret is kept secure in backend code.
This code uses the http-proxy node package:
var http = require('http'),
httpProxy = require('http-proxy')
infura_api_key = process.env['INFURA_API_KEY']
infura_secret = process.env['INFURA_SECRET']
//
// Create your proxy server.
//
proxy = httpProxy.createProxyServer({})
// proxy.on('proxyRes', function(proxyRes, req, res, options) {
// console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
// });
var server = http.createServer(function (req, res) {
// You can define here your custom logic to handle the request
// and then proxy the request.
proxy.web(req, res, {
target: 'https://mainnet.infura.io',
changeOrigin: true,
auth: infura_api_key + ':' + infura_secret,
})
})
console.log('listening on port 8000')
server.listen(8000)
See: https://www.npmjs.com/package/http-proxy.
The code first gets the Infura API key and secret from the environment (you could also use the dotenv package for this).
Then it creates a proxy server object and, later, creates the HTTP server with the options it needs to know the real target of the requests. The important part is adding the “auth:” header to requests between the proxy and Infura. This way the browser code can be written to not use an “auth:” header because it is added in by the proxy on the way through.
Note that above, we create the proxy server in two steps to allow flexibility to add event handlers to log or change the requests or the responses. See the proxy.on example above.
If, for example, you run the above code locally, with this command:
~$ node index.js
listening on port 8000
Then you can write browser-based JavaScript to send requests to:
http://localhost:8000/v3/**YOUR-API-KEY**
You also have the option to access the proxy server on HTTPS. However, you will need to generate appropriate SSL certificates and add them as an option when the proxy server is created. See the http-proxy documentation for details.