CORS (Cross-Origin Resource Sharing) is a mechanism that allows a web application on one domain to access resources on another domain. This is crucial when developing an application where the frontend and backend are separate and communicate through an API.
Hereโs an article explaining CORS implementation in Node.js and Express without using external libraries:
"use strict";
/*jshint node:true */
var simpleMethods, simpleRequestHeaders, simpleResponseHeaders, toLowerCase, checkOriginMatch, origin;
Object.defineProperty(exports, "simpleMethods", {
get: function () {
return [
"GET",
"HEAD",
"POST",
"PUT",
"DELETE"
];
}
});
simpleMethods = exports.simpleMethods;
Object.defineProperty(exports, "origin", {
get: function () {
return ["http://localhost:3000"];
}
});
origin = exports.origin;
Export simpleMethods: Defines the allowed HTTP methods for CORS requests (e.g., GET, POST, PUT, etc.).
Export origin: Specifies the list of permitted origins for access. In this example, http://localhost:3000 is allowed.
Object.defineProperty(exports, "simpleRequestHeaders", {
get: function () {
return ["accept", "accept-language", "content-language", "content-type", "authorization", "token"];
}
});
simpleRequestHeaders = exports.simpleRequestHeaders;
Object.defineProperty(exports, "simpleResponseHeaders", {
get: function () {
return ["cache-control", "content-language", "content-type", "expires", "last-modified", "pragma"];
}
});
simpleResponseHeaders = exports.simpleResponseHeaders;
Export simpleRequestHeaders: Defines allowed request headers from clients in cross-domain requests.
Export simpleResponseHeaders: Defines response headers permitted from server to client.
checkOriginMatch = function (originHeader, origins, callback) {
if (typeof origins === "function") {
origins(originHeader, function (err, allow) {
callback(err, allow);
});
} else if (origins.length > 0) {
callback(null, origins.some(function (origin) {
return origin === originHeader;
}));
} else {
callback(null, true);
}
};
Function checkOriginMatch: Checks if the request origin matches an allowed origin list. If matched, the request is permitted.
exports.create = function (options) {
options = options || {};
options.origins = options.origins || origin;
options.methods = options.methods || simpleMethods;
Initialization of origins and methods options, with default values from origin and simpleMethods if none are provided.
Setting Request and Response Headers
if (options.hasOwnProperty("requestHeaders") === true) {
options.requestHeaders = toLowerCase(options.requestHeaders);
} else {
options.requestHeaders = simpleRequestHeaders;
}
if (options.hasOwnProperty("responseHeaders") === true) {
options.responseHeaders = toLowerCase(options.responseHeaders);
} else {
options.responseHeaders = simpleResponseHeaders;
}
Sets allowed request (requestHeaders) and response (responseHeaders) headers. Converts any given request or response headers to lowercase.
Additional Middleware Configuration
options.maxAge = options.maxAge || null;
options.supportsCredentials = options.supportsCredentials || false;
if (options.hasOwnProperty("endPreflightRequests") === false) {
options.endPreflightRequests = true;
}
maxAge: Specifies the maximum cache age for CORS preflight. supportsCredentials: Determines if the server supports credentials (cookies or tokens) in cross-domain requests. endPreflightRequests: Decides if the server should terminate preflight requests (OPTIONS) or proceed to the next middleware.
return function (req, res, next) {
if (!req.headers.hasOwnProperty("origin")) {
next();
} else {
checkOriginMatch(req.headers.origin, options.origins, function (err, originMatches) {
if (err !== null) {
next(err);
} else {
var endPreflight = function () {
if (options.endPreflightRequests === true) {
res.writeHead(204);
res.end();
} else {
next();
}
};
Function endPreflight: Ends preflight (OPTIONS) requests if endPreflightRequests is set to true. Origin Check: Uses checkOriginMatch to verify if the request origin matches an allowed origin.
Handling Preflight Requests (OPTIONS)
if (req.method === "OPTIONS") {
if (!req.headers.hasOwnProperty("access-control-request-method")) {
endPreflight();
} else {
requestMethod = req.headers["access-control-request-method"];
if (req.headers.hasOwnProperty("access-control-request-headers")) {
requestHeaders = toLowerCase(req.headers["access-control-request-headers"].split(/,\s*/));
} else {
requestHeaders = [];
}
methodMatches = options.methods.indexOf(requestMethod) !== -1;
if (!methodMatches) {
endPreflight();
} else {
headersMatch = requestHeaders.every(function (requestHeader) {
return options.requestHeaders.includes(requestHeader);
});
if (!headersMatch) {
endPreflight();
} else {
if (options.supportsCredentials) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
} else {
res.setHeader("Access-Control-Allow-Origin", "*");
}
if (options.maxAge !== null) {
res.setHeader("Access-Control-Max-Age", options.maxAge);
}
res.setHeader("Access-Control-Allow-Methods", options.methods.join(","));
res.setHeader("Access-Control-Allow-Headers", options.requestHeaders.join(","));
endPreflight();
}
}
}
}
Request Method & Headers Matching: Checks if request method and headers match those allowed. CORS Response Headers: Sets CORS headers like Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, etc.
Exposing Headers in the Response
} else {
if (options.supportsCredentials) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
} else {
res.setHeader("Access-Control-Allow-Origin", "*");
}
exposedHeaders = options.responseHeaders.filter(function (header) {
return !simpleResponseHeaders.includes(header);
});
if (exposedHeaders.length > 0) {
res.setHeader("Access-Control-Expose-Headers", exposedHeaders.join(","));
}
next();
}
}
});
}
};
} else {
if (options.supportsCredentials) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
} else {
res.setHeader("Access-Control-Allow-Origin", "*");
}
exposedHeaders = options.responseHeaders.filter(function (header) {
return !simpleResponseHeaders.includes(header);
});
if (exposedHeaders.length > 0) {
res.setHeader("Access-Control-Expose-Headers", exposedHeaders.join(","));
}
next();
}
}
});
}
};
Access-Control-Expose-Headers: Sets response headers that are accessible to the client if there are custom headers not included in simpleResponseHeaders.
This is how you can implement custom CORS in Node.js without using any library. For the complete script, you can refer to this example.
Top comments (0)