BlogAll Blog Posts | Next Post | Previous Post
Monday, June 5, 2023
What is CORS?
If you've not run into it yet, CORS usually presents itself as an error in your web application that comes up
when you're trying to access data from a remote source. It's an acronym, of course. Cross-Origin Resource
Sharing. In its simplest form, what it is referring to is a security feature that comes into play when a web
application that is hosted on one website tries to access content that is hosted on another website. The
"origin" in this case refers to the website that is hosting the original application. Cross-Origin is referring
to how it is trying to access something somewhere else. And the resource sharing bit is just describing the
general intent around accessing files of some kind by multiple clients. So that's not all that helpful.
Let's say we have a TMS WEB Core app that has been deployed using an Apache web server on a website called
app.example.com. Let's say our app wants to access some files, maybe some images for example, that are stored
on a different Apache or XData web server, let's call that one data.example.com. It doesn't really matter which
web servers are being used in either case. The issues are the same. When our app contacts data.example.com, it
passes along a bit of information, including the fact that it was launched from the app.example.com website,
what it refers to as its "origin". Depending on how the data.example.com website is configured, it may decide
whether to allow access to whatever our app is asking for, basing its decision around this "origin" value. If
nothing is configured, such access is automatically denied. Much of what we'll be covering in this post relates
to that decision mechanism and how we can make changes to it to gain the access we need. And if we can't do
that, then we'll look at how to avoid being in that position in the first place.
CORS is by no means a new problem. It has been around for some time, causing trouble all the while. In fact,
here are a couple of CORS-related posts, one from this very blog originally from 2015, and one from the TMS
Support Center, just in the past month or so. Just an example of how prevalent CORS has been, even in this
corner of the internet. Good material to review before continuing.
It's Not Me, It's You.
So the first big takeaway is that CORS is effectively a server-side security mechanism. If CORS is not
properly configured on the server, then that's a pretty good place to focus our attention. There are situations
where we can't do anything about the server, so we'll cover that a bit later. This also means that there
generally isn't any kind of configuration or option or setting anywhere in our TMS WEB Core project that can
actually help resolve the problem. The problem isn't with our app, but rather with the other end of the
equation - the server we're contacting. This is partly where it gets its bad reputation. When we run into CORS
issues, often it is only our app that has a problem. So the thinking naturally is that there's
something wrong with our app that we can change to fix the problem. Which most often actually isn't the case.
But how do we know if the problem is with CORS or with something else? Well, CORS deservedly gets blamed for a
lot of problems, because it indeed is the source of many of them, but it isn't the cause of all of the
problems. We should be very careful to rule out other potential problems first before diving head-first
down the CORS rabbit hole. And, as it turns out, that's pretty easy to do. Let's say that we want our app
running on app.example.come to download an image from data.example.com. Maybe the URL is something like https://data.example.com/photos/camel.jpg.
If we're encountering an error in our app, the first and most obvious thing to try would be to plunk that URL
into a browser and see if it works.
So why is this important? Well, it means that we can test whether
there are any other problems that are blocking our access to the resource - an image in this case. And there
are lots of things we can rule out with such a test, all of which have nothing to do with CORS. So, if entering
the URL in the browser doesn't get us the expected image, then potentially there is something else afoot that
likely means that our app won't be able to access the image either, regardless of CORS.
Here are some examples of why a URL might not work. If it does work, we've ruled out all these problems. If
it doesn't, then addressing the problem here might solve our app access issue without even having to think about
- The URL could have a typo in it. Really basic problem to have, but also pretty common. Be sure to check whether your URL has any elements that are mixed case, invalid URL characters like spaces, and that the host name is correct. Sometimes when accessing other resources, a different hostname might be used, like photos.example.com or api.example.com rather than www.example.com
- Check the protocol - HTTP vs. HTTPS. Any public-facing production server should be accessed only by SSL these days, but there are still a few stragglers. There can be problems with "mixed content" - data coming from both HTTP and HTTPS sources displayed on the same page, so best to get everything shuffled over to the HTTPS side if you have any say in the matter.
- Check that HTTPS is properly configured. SSL certificates expire all the time, and even some of the largest
organizations have overlooked renewals from time to time. Setting up a server with an SSL certificate is also
not entirely free from problems, so be sure to check that your server has a valid SSL Certificate. For XData
servers, this means using the TMS HTTP Config Tool to assign an SSL certificate to a particular URL/port on
the system. For Apache, this involves setting up specific sections of the configuration to point at wherever
the SSL certificate files might be stored. Ideally, these would be configured in a way that auto-renewal is
as automated as possible.
- Server firewall settings can certainly interfere. If you've just configured a new Apache web server, or a new XData server or something along those lines, or changed a port number, be sure to check that the firewall running on the system is configured to allow access to it. It can also be the case that the firewall blocks certain IP addresses, so be sure that the system you're using to test isn't blocked by adding it to a whitelist, if automatic blocking is enabled.
- Client firewall settings aren't usually the cause of interference, but depending on what you're doing, they can be. Antivirus software and other desktop-protection-style tools can introduce all kinds of blocks that aren't immediately obvious. This is often the case, particularly when dealing with mail and SMTP, but it can also be an issue when accessing web content on unusual ports.
- Accessing HTTPS sites in the local network can sometimes be a problem. If you've got a server configured in
your network that is accessed through your router via NAT, for example, this often doesn't play very nicely
with HTTPS access. This is due to most routers not allowing connections to flow back into the network through
the NAT interface, when they originate from the local network. Best approach here is to access local servers
via HTTP and remote servers via HTTPS. And if you need to test a local HTTPS server, try to do it from a
remote system outside the local network.
- Check that the server application is actually running. This can happen a lot if you're flipping back and forth between a TMS WEB Core project and a TMS XData project, and you forget to leave the XData project running. This can also happen with Apache, as it just takes a small typo in one of its endless configuration files to prevent it from starting up.
- Check that the server has access to serve up the image. Apache running on Linux has layers of security that
determine what it can and can't serve. Often, permissions for a directory tree need to be configured
appropriately for the Apache server to have access to the files.
- Check that the server is configured to allow serving the file types you're interested in. There are settings within Apache that can be configured to restrict access to all but the most commonly used files. In a properly-secured Apache server, in fact, it isn't going to allow access to anything unexpected. So if you've got files with an unusual extension, it might be that you have to do some configuring for those to be served. Or maybe even if it isn't so unusual, like .json or .csv.
- Often, access to a resource is restricted somehow, maybe requiring an authentication token like a JWT, or some other API key. This makes it a bit harder to test with a browser as there may not be any place to stick such a token. But we've got options here. Using the command-line 'curl' command, we can pass the authentication information using the -H parameter (header). If we're using XData with Swagger enabled, we can also use that to enter authentication information, also assuming that it is an XData endpoint that we're trying to access. TMS recently released their free REST Insight tool which can be used in a similar way, great for when you're accessing someone else's server or when you don't have Swagger enabled.
Ideally, you should be able to largely mimic whatever your app is doing to get access to the remote content. The
good news is that if your URL works, you can check off all of them at once. And there are no doubt other items
that could be added to this list. If anyone posts a comment about something obvious I've overlooked, I'd be
more than happy to update the list. The purpose of this browser test, though, is to ensure that you've actually
got a CORS problem and not something else hiding behind an obscure error that might be easily mistaken for
CORS. If you can't get access to the resource outside of your app through some mechanism like a browser or curl
or a REST interface, there's little chance your app will be able to get access either, hence why it is important
to do this most basic check first.
So You Think You've Got a CORS Problem.
Assuming that you've tested and were able to access the resource without problems using the browser or curl or
a REST interface, but your app still can't connect, then it may indeed be that you have a CORS problem. So what
does a CORS problem actually look like? Usually, it is just an obscure error in the browser console, and they
can sometimes be very misleading. Here are a few examples.
In this case, we get a "blocked by CORS policy" but it is entirely misleading. The reason this error is
generated is actually because the resource simply isn't available. This is an error generated from our recent Icon
Picker blog post when using the Font Awesome icons. In this case, a slew of Font Awesome icons are
downloaded after performing a search. Some of the icons returned by their API, however, refer to icons that no
longer exist. In this case, a "penny-arcade" icon used to be included in Font Awesome 5, but is not part of
Font Awesome 6. So they've got an error in their API, essentially. This error, then, is a bit of a nuisance
that really has nothing to do with CORS. It is ultimately just a "file not found" error and can be safely
ignored. Sure isn't very friendly, though. Even less so as it is repeated for each icon that is missing in
This is one we might very well encounter more often. If we create a TMS WEB Core app (running on the default TMS Web Server port 8000) and use it to try and connect to a TMS XData server on our same development system (in this case, running on port 12345), and CORS has not been enabled in XData, this is the error that will result. It is not so simply stating that your app, running on http://localhost:8000 (our equivalent of app.example.com) is trying to access a resource on a web server running on http://localhost:12345 (our equivalent of data.example.com). And because CORS has not been configured, this fails. It tries to suggest an alternative - passing a header that includes 'mode: no-cors' but this is unlikely to be what we want to do. The "opaque" response means that our app won't be able to read the data in that case. So not very useful.
And that's where a lot of the frustration comes from. Copying and pasting the URL from our fetch() command
Server Configuration Accessible.
If we've identified a CORS problem with our app, the next obvious step is to try and figure out what to do about it. And as we mentioned earlier, this is most often a server configuration issue. How difficult it will be to resolve depends largely on whether you have any control over the server in question. Assuming the server configuration is accessible, we can hopefully make quick work of this. If the server configuration is not accessible (either by you, your ISP, or someone in your organization) then we've got a bigger problem. We'll address that in the next section.
What is it that needs to be configured? That's where the error message is actually helpful. There is something called the 'Access-Control-Allow-Origin' header that is responsible for all of this. To be clear, there is more to CORS than this one header value, but it is all we'll need to be concerned about at the moment. This is a header value that is returned in the response to a web server request. Our app (or the browser on behalf of our app) makes a request of the remote web server, passing in information in its own header, like the 'origin' value and 'content-type' that describes what we're sending. The remote web server checks over our request and sends back a response. If the response doesn't include the 'Access-Control-Allow-Origin' header, and it is for a different remote server than what our own 'origin' is, then the request will fail - the browser will not be able to continue with the request.
Making this more complicated, this exchange can happen more than once. In all but the simplest of requests,
the browser will send a "preflight" request using an "OPTION" method (as opposed to the GET or POST method of
the anticipated request). This preflight request is performed to check if the actual request, where data is
being transmitted, is actually permitted. And it is usually here that the 'Access-Control-Allow-Origin'
situation comes up.
What can we do about it? Plenty. The most obvious thing is to simply configure the server to add the
'Access-Control-Allow-Origin' header to the responses it is sending back. There are various rules here, but
generally, this header will have one of two values. If you, as the server owner, don't much care who's making
requests, and you're not using HTTP authentication, then this header can be set to an asterisk, and we can call
it a day. If you do care who's making requests, then this can be set to match the origin of where those
requests are coming from, like app.example.com. This means that only our app can access the resource in
question. This can be a bit of a problem if you have multiple applications connecting to the same resource and
they change frequently, but something to keep in mind.
If your server is an XData server, this is not too difficult to configure at all. In the default XData project, there is a ServerContainer unit (Unit1.pas) that has the XDataServer component. This is where CORS can be configured.
- Open the ServerContainer Unit (Unit1.pas)
- Right-click on the XDataServer Component and select "Manage middleware list". Double-clicking has the same effect.
- Right-click on an empty part of the list window that appears, and select 'Add middleware'
- Select CORS from the available middleware choices.
- Select CORS in the middleware list after it has been added, so that the Object Inspector shows its options.
- Enter an asterisk into the Origin property.
XData CORS Configuration.
There are some other great middleware components in there as well. In particular, Compress is another option
that could well be included most of the time. With CORS added, building and running the XData server app is all
that is left to do. Now, when a request comes along, the 'Access-Control-Allow-Origin' header will be present,
populated with whatever value you entered into the Origin property in the Object Inspector. Easy, right?
If your remote web server is running Apache, then adding in the 'Access-Control-Allow-Origin' header is
mostly a matter of finding the best place to put it. The code that you need to add is the following.
<IfModule mod_headers.c> Header set Access-Control-Allow-Origin "*" </IfModule>
Depending on what kind of Apache configuration is involved, the best place for this might very well be within the <VirtualHost> directive. If it is just a particular folder that needs to be configured in this way, a <Directory> directive might be another good choice, or even an .htaccess file. Or the main httpd.conf file (sometimes called apache2.conf) if this is to be applied across everything managed by Apache. It may be necessary to restart Apache or at least instruct it to reload its configuration files. None of this should be particularly difficult if you're already familiar with how to configure Apache. If this isn't familiar territory, there are plenty of online resources to help. In fact, there's an entire website dedicated just to enabling CORS in various web servers: Enable CORS.
There are plenty of other CORS-related headers and other functionality that can be configured using this same
approach, but setting this one header will at least get things moving again.
Server Configuration Inaccessible.
What if the remote web server isn't one that you control? Let's assume that it is owned by someone else, and they're not taking your calls. How do we get the request to the remote server in a way that the browser will accept? One approach is to use a CORS proxy. What is a CORS proxy? It's a separate web server (or web service might be more accurate) that takes your remote URL and makes the request on your behalf, passing back the response. A CORS proxy isn't a web browser, so it isn't bound by the same rules that a browser is. It can simply ignore whatever the 'Access-Control-Allow-Origin' situation is, and make requests regardless. It then returns the data to your browser with the necessary CORS headers so that your browser thinks everything is just fine. Almost makes CORS seem like a waste of time!
There are freely available CORS proxy services. Be mindful of these, however, as the proxy service will be able
to see everything passing through it. You wouldn't want to use such a service for anything that was in any way
meant to be private. This includes anything using an API key or other credentials. It might work in a pinch,
but this isn't really a production solution to any CORS problem.
Running your own proxy using one of the GitHub repositories, like cors-anywhere, might be a workable solution. In this instance, you're running essentially another web server of your own,
and any requests destined for a CORS-deficient remote server can instead be passed to this proxy server. It
will respond in the same way, with the necessary CORS header information. The main risk here is ensuring that
this is configured only for your own use. Some of the popular configurations are designed to make it easy to
pass any request through the proxy and have it bypass the CORS situation, by just including the entire URL as
part of what is sent to the proxy. Some care should be taken to ensure that only your applications are
permitted to use this proxy, using, in the case of cors-anywhere, the available whitelist feature and other
Another option, if you're already using XData in your project, is to use an XData endpoint as a CORS proxy. This endpoint can be fashioned in such a way that it takes your request as a parameter, makes the request on your behalf, and returns whatever results it receives. It's a proxy, after all, that's what proxies do. Similar to the cors-anywhere solution, this is something that would run on your own systems. And you can even add your own security - maybe it can only be used by those who are logged in. Just add the [authorize] attribute in the endpoint's interface declaration, and you're all set. So long as you've already set up the necessary JWT middleware with a corresponding login function. The endpoint is then very simple.
In our Icon Picker example, we set it up to just have the Font Awesome request passed in. But it could be
extended to also include the remote URL. Something like the following.
function TSystemService.CORSProxyPost(URL: String; ContentType: String; Body: String): TStream; var Client: TNetHTTPClient; DataStream: TStringStream; Response: String; begin DataStream := TSTringStream.Create(Body); Client := TNetHTTPClient.Create(nil); Client.Asynchronous := False; Client.ContentType := ContentType; Client.SecureProtocols := [THTTPSecureProtocol.SSL3, THTTPSecureProtocol.TLS12]; Response := Client.Post(URL,DataStream).ContentAsString; Result := TStringStream.Create(Response); Client.Free; DataStream.Free; end;
Now, this can be called just by passing the URL and whatever Content-Type and Body that needs to be included.
Whatever is returned by the remote server is sent back as a TStream. This is ideal when you're sending JSON to
a remote server and expecting JSON back. Other parameters could be passed if custom headers were needed, like
an authorization header, for example. Just be sure to enforce some kind of access control so that it doesn't get
used by anyone else. And of course, be sure to enable CORS for this XData server.
With CORS having various workarounds available, what was the original intent behind it? Prior to CORS, web applications were blocked from accessing remote content, just as we find now when CORS headers are not provided. This was a security protocol that was intended to prevent malicious websites from stealing user data. But that's an ongoing battle, as new and creative ways to infiltrate websites and steal user data are a continuing threat. As more of our lives effectively moves online, the number of bad actors, and their levels of sophistication, continue to escalate.
For our purposes, we're usually about trying to get things to work, not so much about the gritty details of
security. Not because it isn't important, but rather because if our app doesn't work, all the security in the
world isn't going to make a difference. And that's a whole other topic for another day. More acronyms! Here's
what ChatGPT had to say in response to "What security risks does CORS address?":
CORS (Cross-origin Resource Sharing) addresses various security risks, especially related to cross-site scripting(XSS) and cross-site request forgery(CSRF) attacks. By allowing cross-domain requests to be made in a controlled, secure, and standardized way, it helps mitigate the following risks:
1. XSS(XSS): Cross-site scripting can occur when an attacker injects malicious scripts into a web application. It is a significant security risk as these scripts can steal sensitive user data or even hijack user sessions. CORS helps prevent XSS attacks by restricting cross-origin requests that could access sensitive data based on the same-origin policy.
2. CSRF: CSRF attacks happen when an attacker tricks a user into performing actions on a website or web application without their knowledge. CORS addresses CSRF risks by specifying which domains are allowed to submit a particular request and which HTTP method can be used.
4. Protecting user credentials: CORS prevents unauthorized parties from accessing protected user information by requiring the use of specific HTTP headers for requests that contain user credentials. In summary, CORS helps prevent unauthorized access to resources and data across multiple domains, thereby mitigating Cross-Site Scripting and Cross-Site Request Forgery attacks.
Not sure what its third point was? Hopefully it wasn't too important! Regardless, the point here is that
there is a lot more to the story than what we've covered. There are more CORS options, various rules that are
applied in certain conditions, and even a few performance-related tweaks that can be employed (caching preflight
checks). But if you're stuck on a CORS issue in your TMS WEB Core project, by all means, please post a question to the
TMS Support Center. Or, check out this website, one of the more pleasant sites covering CORS information: Will
As always, questions, comments, and feedback are much appreciated.
This blog post has received 4 comments.
All Blog Posts | Next Post | Previous Post