CORS Proxy is a NodeJS reverse proxy server which adds CORS headers to the proxied request.
This project is a direct derivative work of CORS Anywhere.
The url to proxy is literally taken from the path, validated and proxied. URL is validated with URL.canParse()
method of NodeJS URL API.
This package does not put any restrictions on the http methods or headers, except for cookies (read more). Requesting user credentials is disallowed. The app can be configured to require a header for proxying a request, for example to avoid a direct visit from the browser.
For most use cases CORS Anywhere provides all functionality you may need. However, I needed to modify the response headers before sending it to a user. If that's what you are looking for, feel free to give it a try!
Main differences between CORS Proxy and CORS Anywhere:
- Added
isEmptyOriginAllowed
callback/boolean option to identify is it allowed or not to specific request to be proxied ifOrigin
header is empty. (eg. when you proxying files that can be opened in browser tab) - Added
handleResponse
callback option to modify response before sending it to user. - Added
isAllowedToFollowRedirect
callback/boolean option to identify is it allowed or not to follow redirects that target sends. - Added
X-Proxy-*
headers - Better autocompletion of
createServer()
options types and added descriptions for each option
# Clone git repository
git clone https://github.com/kurrx/cors-proxy.git
cd cors-proxy
# Install dependencies
npm install
# Create .env from example, and edit it
cp .env.example .env
# Start development server
npm run start
You can edit server.js
file in root directory to change the way server proxies requests. All available options you can see here.
import createServer from './src/index.js'
import 'dotenv/config'
const server = createServer({
proxyHttps: process.env.NODE_ENV === 'production',
originWhitelist: process.env.ORIGIN_WHITELIST?.split(', ') || [],
originBlacklist: process.env.ORIGIN_BLACKLIST?.split(', ') || [],
})
const PORT = process.env.PORT || 4001
server.listen(PORT, () => {
console.log(`[Server] Running at http://localhost:${PORT}`)
})
Request examples:
http://localhost:4001/http(s)://google.com/any/nesting/level
- google.com with CORS headershttp://localhost:4001/http://localhost:8080/
- localhost:8080 with CORS headershttp://localhost:4001/invalid
- Replies 400 Bad Request becauseinvalid
is not valid URL
Extends client IncomingMessage
request with proxyState
object. You can access this object with req.proxyState
(do not modify these options, you may break some internal logic).
interface IncomingMessageWithProxyState extends IncomingMessage {
proxyState: {
proxyBaseUrl: string
location: URL | null
origin: string
headers: Record<string, string>
redirectCount: number
}
}
-
proxyBaseUrl
- Base URL of the proxy server. Used to change redirect location to the proxy server.
Example: If target responds with 3XX code and withhttps://google.com
location header (in case when following redirect is not allowed), then we modify it tohttp://localhost:4001/https://google.com
. -
location
- ParsedURL
object of the target url. If url is not valid, then it will benull
. -
origin
- origin of the request. If header is not present, then it will be empty string. -
headers
- Headers that is sent to the target. -
redirectCount
- Number of redirects followed.
src/index.js
exports createServer(options)
function, which creates a server that handles proxy requests. The following options are supported:
type RequestCallback = (
req: IncomingMessageWithProxyState,
res: ServerResponse,
) => boolean
type ResponseCallback = (
req: IncomingMessageWithProxyState, // Client request
res: ServerResponse, // Client response
proxyReq: IncomingMessage, // Proxy request
proxyRes: ServerResponse, // Proxied response
) => boolean
interface Options {
proxyHttps?: boolean
maxRedirects?: number
originWhitelist?: string | string[]
originBlacklist?: string | string[]
redirectSameOrigin?: boolean
requireHeaders?: string | string[]
removeHeaders?: string | string[]
addHeaders?: Record<string, string>
corsMaxAge?: number
handleInitialRequest?: RequestCallback | null
isEmptyOriginAllowed?: RequestCallback | boolean
checkRateLimit?: RequestCallback | null
handleResponse?: ResponseCallback | null
isAllowedToFollowRedirect?: ResponseCallback | boolean
httpProxyOptions?: HttpProxyOptions
}
-
proxyHttps
- Flag that identifies is proxy served with https or not. Used to configure redirect link to proxy server. Example: Your proxy server domain isexample.com
if flag is set totrue
, then in case of 3XX response from target your redirect link will behttps://example.com/https://target.com
(Default:false
) -
maxRedirects
- Maximum number of redirects to follow (default:5
) -
originWhitelist
- If set, requests whose origin is not listed are blocked. If this list is empty, all origins are blocked (default:[]
) -
originBlacklist
- If set, requests whose origin is listed are blocked (default:[]
) -
redirectSameOrigin
- If true, requests to URLs from the same origin will not be proxied but redirected. The primary purpose for this option is to save server resources by delegating the request to the client (since same-origin requests should always succeed, even without proxying). (default:false
) -
requireHeaders
- If set, the request must include this header or the API will refuse to proxy. Recommended if you want to prevent users from using the proxy for normal browsing (default:[]
) -
removeHeaders
- Exclude certain headers from being included in the request (default:['cookie']
) -
addHeaders
- Set headers for the request (overwrites existing ones, but can be overwritten byX-Proxy-*
headers) (default:{}
) -
corsMaxAge
- If set, an Access-Control-Max-Age request header with this value (in seconds) will be added (default:0
) -
handleInitialRequest
- If set, it is called right after parsing url. If the function returns true, the request will not be handled further. Then the function is responsible for handling the request. This feature can be used to passively monitor requests, for example for logging it should returnfalse
(default:null
) -
isEmptyOriginAllowed
- If set as callback, it is called whenOrigin
header is empty to decide is it allowed to proxy current request or not (default:true
) -
checkRateLimit
- You can implement rate limiting here. If this function returns a non-empty string, the request is rejected and the string is send to the client (default:null
) -
handleResponse
- If set, it is called before sending response to the user. If the function returns true, the response will not be sent to the user. Then the function is responsible for handling the response. This feature can be used to add some headers before sending it to user, for example you can setContent-Disposition
header to make file downloadable from browser if target sends file, in that case you should returnfalse
(default:null
) -
isAllowedToFollowRedirect
- If set as callback, it is called when target responds with 3XX status code to decide to follow redirect or not (default:true
) -
httpProxyOptions
- Under the hood, http-proxy is used to proxy requests. Use this option if you really need to pass options to http-proxy. The documentation for these options can be found here. (default:{ xfwd: false }
)
This feature designed to overwrite browsers list of disallowed headers (eg. setting custom User-Agent
on browser will throw an error). You can set X-Proxy-*
headers to overwrite original headers before sending request to the target.
Example: Client is sending request with User-Agent
header, but target rejects request for some reason. In that case you can set X-Proxy-User-Agent
header with other value and it will overwrite User-Agent
header before sending request to the target.
Cookies are not proxied. If the target sets a cookie, it will be deleted from response headers. If the client sends a cookie, it will be deleted from response headers (default of removeHeaders
option). This is because the server is not intended to be used for normal browsing, and it is not safe to proxy cookies. But there are few workarounds for that:
- You can set
X-Proxy-Cookie
header to overwriteCookie
header before sending request to the target. - You can use
rewriteCookies()
function that is exported fromsrc/utils.js
inside ofhandleResponse
callback to rewrite cookies before sending response to the user. Basically this function rewrites allSet-Cookie
headers sent from the target toX-Proxy-Set-Cookie-{num}
headers and deletes originalSet-Cookie
headers. Wherenum
is number from1
toX-Proxy-Cookies-Count
. Then you can useX-Proxy-Set-Cookie-{number}
headers to store cookies on the client side. - Use
cookieDomainRewrite
option fromhttp-proxy
to rewrite cookies domain before sending it to the client:
You also need to commentdelete proxyRes.headers['set-cookie']
inside ofsrc/handle.js
file to make it work.
I do not recommend to use cookies with this server, but if you really need to use it, then you can use one of the workarounds above.
To use the API, just prefix the URL with the API URL. You can use like that:
const corsProxiedApi = axios.create({
baseURL: 'http://localhost:4001/https://api.corsless.com/',
})
// http://localhost:4001/https://corsless.com/endpoint
corsProxiedApi.get('/endpoint').then(res => {
console.log(res.data)
})
The MIT License
Copyright (C) 2013 - 2021 Rob Wu rob@robwu.nl
Copyright (C) 2024 Kurbanali Ruslan ruslan@kurr.dev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.