This is a lightweight HTTP proxy server built using the Sinatra framework. It acts as a pass-through proxy, allowing requests to be forwarded to a specified target URL. Additionally, it provides a JWT (JSON Web Token) verification mechanism to allow only specific requests.
Powered and used in production by Bump.sh
- CORS Support: Handles CORS headers, allowing cross-origin requests.
- JWT verification: Verifies the presence and validity of the
x-cors-toujours-token
header to ensure requests are allowed. - Flexible HTTP Method Support: Supports
GET
,POST
,PUT
,PATCH
, andDELETE
methods for forwarding client requests to the target server. - Automatic Request Forwarding: Forwards requests to the specified target URL while preserving headers and request bodies.
- Path Parameter Support: Supports dynamic path parameters in URL patterns (e.g.,
/posts/{post_id}/comments/{id}
).
- Ruby (>= 3.0)
- Bundler
Install the required gems:
bundle install
The proxy is configured via environment variables. In local or test environment you can set those variables thanks to a .env
file.
Use the following script to create your first JWT signing keys:
./rotate_keys.rb
This will generate a RSA key pair and add it to the .env
file with the following variables:
JWT_SIGNING_PUBLIC_KEY
: Public key for token verification (used by the proxy)JWT_SIGNING_PRIVATE_KEY
: Private key for token signing (used by the server exposing a web HTTP client allowing only specific requests)
If later on you need to rotate the keys you will need to remove them manually from the .env file before exectuting the script again.
By default, the proxy will read the verification token from the header x-cors-toujours-token
. You can change the name of the header by adding the following environment variable to your .env
file:
CORS_TOUJOURS_TOKEN_HEADER="x-my-custom-header-name"
Run the following command to start the server on port 4567:
bundle exec puma
Run the following command to run the test suite:
bundle exec rspec --color -fd spec
The server verifies the x-cors-toujours-token
header for every request (name of the header is customizable - see configuration details). The JWT token must contain the following claims:
servers
: Array of allowed target server URLsverb
: Allowed HTTP method for the request to be made (GET, POST, PUT, PATCH, or DELETE)path
: Allowed path pattern, supporting path parameters (e.g.,/posts/{post_id}
)exp
: Token expiration timestamp
E.g.: A client sending a request to GET https://bump.sh/api/v1/ping
via the proxy will need to include an encoded JWT token in
the x-cors-toujours-token
header, whose decoded value would look like:
{
"servers": ["https://bump.sh"],
"path": "/api/v1/ping",
"verb": "GET",
"exp": "2025-01-01T00:00:00Z"
}
If the token is missing, invalid, or doesn't meet those requirements,
the request will be rejected with a 403 Forbidden
HTTP error.
The server supports dynamic path parameters in URL patterns. For example:
- Pattern:
/posts/{post_id}/comments/{id}
- Valid URL:
/posts/123/comments/456
GET request:
curl -X GET "http://localhost:4567/https://jsonplaceholder.typicode.com/todos" \
-H "x-cors-toujours-token: YOUR_JWT_TOKEN"
PATCH request:
curl -X PATCH "http://localhost:4567/https://jsonplaceholder.typicode.com/posts/1" \
-H "Content-Type: application/json" \
-H "x-cors-toujours-token: YOUR_JWT_TOKEN" \
-d '{"title":"foo"}'
The server includes the following CORS headers for cross-origin access:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, x-cors-toujours-token, x-requested-with
Preflight OPTIONS requests are handled automatically.
The server returns different status codes based on various error conditions:
-
401 Unauthorized:
- Missing
x-cors-toujours-token
header - Invalid JWT token
- Expired token
- Missing
-
403 Forbidden:
- HTTP method doesn't match the token's
verb
claim - Target server not in the token's
servers
list - Request path doesn't match the token's
path
pattern
- HTTP method doesn't match the token's
-
502 Bad Gateway:
- Issues communicating with the target server
Each error response includes a JSON body with an error
field describing the specific error.
This project is licensed under the AGPL License.
Feel free to open issues and submit pull requests!