CORS with WSO2 Identity Server 5.11
- Oshan Mudannayake
- Job Title - WSO2
What is CORS?
If you are here, chances are you know what CORS is. But let’s go through the basics in case you are a bit rusty on the exact way that it works. You can hop over to the next section if you feel confident about your knowledge.
Cross-origin resource sharing (aka CORS) is simply a mechanism for controlling access to a resource located outside of a given domain. So, what is actually a ‘given domain’? Allow me to illustrate this in a simpler manner!
Assume that you are browsing the content of a website hosted in a specific domain. When you try to retrieve resources from a web server in the same origin, that is a “same-origin request”. “Cross-origin request” is when you try to retrieve a resource from a different domain than the one that you are in. The important question is, why does it matter whether a request is from the same origin or a different origin.
Why CORS?
CORS is actually a security measure that prevents unwanted access to APIs. Assume that you got an API at api.mydomain.com. Whether or not you should allow any domain to access your API depends on the authentication method. Assume the authentication to the api.mydomain.com is based on session cookies. So a user who has previously accessed your API through the website gooddomain.com can have the session cookies saved in the browser. If api.mydomain.com allows requests from any domain, another website baddomain.com will be able to send requests to api.mydomain.com using AJAX even without the permission of the user. It doesn’t matter if the request originates from the baddomain.com as browsers automatically attach cookies associated with the destination domains. This is why it is important to be able to control access to the domains that can send requests to your APIs.
How CORS?
A simple breakdown of a CORS request flow is as follows.
- The user takes an action that triggers a request A to a different domain.
- The web browser identifies that request A is a cross-origin request and does not initiate the request.
- The web browser sends another request B called the preflight request to the destination domain Y asking whether it accepts requests from the domain X that the request A originated in.
- The destination domain Y sends back a response to the preflight request B sent by the browser.
- If the preflight request says that the destination domain Y accepts requests from the initial domain X, then the browser proceeds to send the original request A.
For a more detailed explanation of how CORS work, please visit https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.
WSO2 Identity Server and CORS before 5.11
WSO2 Identity Server versions prior to 5.11 enforced CORS using an open-source CORS filter. This filter was applied to the web.xml file of the internal Tomcat server in order to set the CORS configurations. A sample CORS configuration looked like below.
<filter> <filter-name>CORS</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowOrigin</param-name> <param-value>https://mydomain1.com, https://example.com</param-value> </init-param> <init-param> <param-name>cors.supportedMethods</param-name> <param-value>GET, HEAD, POST, OPTIONS</param-value> </init-param> <init-param> <param-name>cors.exposedHeaders</param-name> <param-value>Location</param-value> </init-param> </filter>
<filter-mapping> <filter-name>CORS</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>
There were some limitations to this design. The biggest obstacle was that Identity Server had to be restarted whenever a CORS origin needed to be added to the configurations. It was not possible to configure these settings through the user interface. This could be a serious limitation in an environment where a large number of applications are managed by a single IS instance.
Another obvious shortcoming in the previous CORS filter was that the configurations were universal to all the tenants. There wasn’t any way of defining separate configurations per tenant. This again wouldn’t be much of an issue where there are few tenants and each of them has only several applications. But, it becomes a necessity rather than a nice-to-have when a single IS instance manages a significant number of tenants.
The dawn of a new era for CORS
The requirement for a new design
It was obvious that the existing CORS filter had to be improved in order to get rid of at least some of its limitations. The expectation was that the new CORS management feature will support,
- Modifying the configurations without restarting the Identity Server.
- Configuring separate CORS origins per each application.
The first requirement seemed simple enough. However, the issue was that it was apparent that significant work was required in order to extend the existing CORS filter to read the dynamically set configurations.
Allowing users to configure CORS per application was much more complicated. The browser decides whether a specific CORS request is allowed by sending a preflight request. And the user nor the domain which makes the request does not have any control over what is sent in this preflight request. The IS can only respond to the preflight request using the information added to it by the web browser. So, the only way for Identity Server to identify the application, which sent the request, is through the origin URL of the preflight request.
WSO2 Identity Server does not support application URLs yet. In its current state, the product has partial support for per-tenant-URLs. Therefore, for the time being, the new CORS feature will also have to be supported only at the tenant level. And even that is at the beta level as the tenant URL mode will not be fully available for all the endpoints in Identity Server 5.11.
The new CORS architecture
A new design for the CORS management feature was proposed, considering all the previous requirements and concerns.
In the new design, all the CORS management functionality is achieved using an OSGi service (1). This functionality is made available through the Server Configuration API (4), Application Management API (5) and a new CORS API (6). The custom CORS Valve (2) has replaced the previous CORS Filter where the configurations could only be changed at the deployment stage.
How to use the new feature?
Deployment stage configurations
You can still set up the CORS configuration at the server level using the deployment.toml if that is your cup of tea. As an example, you can set the same configuration mentioned for the web.xml as below.
[cors] allowed_origins = [ “https://mydomain1.com”, “https://example.com” ] supported_methods = [ "GET", "HEAD", "POST", "OPTIONS", ] exposed_headers = [ "Location" ]
Simple enough right? You can check out all the configurations that you can set in here. This function will work regardless of whether you have enabled tenant URL mode in Identity Server or not.
Manage CORS using the REST APIs
These APIs will allow you to set CORS configurations per tenant. But it will only work if the tenant URL mode is enabled.
This API will allow setting separate CORS configurations per each tenant. If these are not specifically set for a tenant, the server level configurations will be used.
Application Management REST API
The previous PUT API call for OIDC inbound will allow updating the allowed CORS origins per application through the allowedOrigins parameter. Even if these are configurable per application, they will still be applied at the tenant level. In other words, when a CORS origin is allowed for an application, it will be allowed for all the applications of that particular tenant.
The new CORS API will facilitate retrieving all the allowed CORS origins per tenant. This can also show you which applications have allowed CORS requests for specific origins.
So that is it about our new CORS functionality. We are pretty excited about it. Please let us know if you find any bugs associated with the CORS functionality.
Yours sincerely,
The author of the new CORS feature