= Enable CORS in Tomcat bundle
:description: How to enable Cross-Origin Resource Sharing (CORS) in Tomcat, and check it.

How to enable Cross-Origin Resource Sharing (CORS) in Tomcat, and check it.

If you try to call the REST API from a page hosted on another domain than the one of the tomcat bundle,
you will face some issues due to the 'same-origin policy' enforced by web browsers.
For instance you may see in your browser a message such as:

____
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at XXX.
This can be fixed by moving the resource to the same domain or enabling CORS.
____

By configuring the CORS filter on the tomcat bundle, you will be able to access the Bonita REST API from a page
 hosted on a different domain from the one of the tomcat bundle.

== Tomcat configuration

=== Add CORS filter

Edit the web.xml of the bonita.war to add the CORS filter:
_*Important Note:* to use this configuration, you will need to replace the `ALLOWED_ORIGIN_LIST` by your own allowed origin list._

[source,xml]
----
<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>ALLOWED_ORIGIN_LIST</param-value>
  </init-param>

  <init-param>
	<param-name>cors.support.credentials</param-name>
	<param-value>true</param-value>
  </init-param>

  <init-param>
    <param-name>cors.allowed.methods</param-name>
   <param-value>GET, HEAD, POST, PUT, DELETE, OPTIONS</param-value>
  </init-param>

  <!-- List of the response headers other than simple response headers that the browser should expose to
    the author of the cross-domain request through the XMLHttpRequest.getResponseHeader() method.
    The CORS filter supplies this information through the Access-Control-Expose-Headers header. -->
  <init-param>
      <param-name>cors.exposed.headers</param-name>
      <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials,X-Bonita-API-Token</param-value>
  </init-param>

  <!-- The names of the supported author request headers. These are advertised through the Access-Control-Allow-Headers header.
    The CORS Filter implements this by simply echoing the requested value back to the browser.
  -->
  <init-param>
      <param-name>cors.allowed.headers</param-name>
      <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,X-Bonita-API-Token</param-value>
  </init-param>

</filter>
...
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
----

_*Important Note 1:* The filter must be inserted in the file webapps/bonita/WEB-INF/web.xml (not in the Tomcat conf/web.xml)_

_*Important Note 2:* It must be the first filter, inserted right after the </error-page> tag_

for more information:
https://tomcat.apache.org/tomcat-8.5-doc/config/filter.html#CORS_Filter

=== Choose cookies SameSite Policy

By default, cookies will be automatically passed with a same-site request, or a cross-site top-level navigation with a "safe" HTTP method. +
This default configuration aims to ensure that we respect the new standard cookie policy rules.

To understand those changes, see https://blog.chromium.org/2020/02/samesite-cookie-changes-in-february.html +
Also note that this new policy will be definitively applied in the months to come for Chrome, and later for the other browsers,
as you can see here https://blog.chromium.org/2020/04/temporarily-rolling-back-samesite.html

If you want to perform "unsafe" CORS requests (which means performing a POST/PUT/DELETE request)
you will need to modify the tomcat `conf/context.xml` file, to set sameSiteCookies to "none" instead of "lax".

[source,xml]
----
    ...
    <!-- default samesite cookies configuration, for CORS set sameSiteCookies to "none" and configure bundle for HTTPS  -->
    <CookieProcessor sameSiteCookies="none" />
    ...
----

[WARNING]
====

Using sameSiteCookies="none" will also force you to use xref:ssl.adoc[secure HTTP (HTTPS) on your Bonita server]. +
To explain this constraint, see https://blog.chromium.org/2019/10/developers-get-ready-for-new.html
====

== HTML Example test page

Here is an example of html page that:

* logs in using the loginservice,
* get the current session, via the REST api resource system/session
* edit a user password using the REST api resource identity/user

This page can be hosted on a different domain, and thanks to the CORS filter, the requests will be successfully processed.

_*Important Note 1:* this example works on a bundle where the xref:csrf-security.adoc[CSRF security filter is activated]. As the header "X-Bonita-API-Token" is set with the "session apiToken"._

[source,html]
----

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>CORS Demo</title>

        <script type="text/javascript">
			var bonitaServerPath;

            function loginToBonita() {
                var myHeaders = new Headers();
                myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

                var urlencoded = new URLSearchParams();
                urlencoded.append("username", document.getElementById("username").value);
                urlencoded.append("password", document.getElementById("current-password").value);
                urlencoded.append("redirect", "false");


                var requestOptions = {
                    method: 'POST',
                    headers: myHeaders,
                    body: urlencoded,
                    redirect: 'follow',
                    credentials: 'include'
                };

                return fetch(bonitaServerPath + "/loginservice", requestOptions)
                    .then(result => {
						if (!result.ok) {
							throw Error(result.status);
						}
						return getAuthToken();})
                    .catch(error => {document.getElementById("error").innerHTML += "<br/> &#x26a0; Login error. " + error;});
            };

            function getAuthToken() {
                var myHeaders = new Headers();
                var requestOptions = {
                    method: 'GET',
                    headers: myHeaders,
                    credentials: 'include'
                };

                return fetch(bonitaServerPath + "/API/system/session/unusedId", requestOptions)
                    .then(response => {
						if (!response.ok) {
							throw Error(response.status);
						}
						return response.headers.get("x-bonita-api-token");})
                    .catch(error => {document.getElementById("error").innerHTML += "<br/> &#x26a0; Unable to retrieve authentication token from session. " + error;});
            };

            function getUserId() {
                var myHeaders = new Headers();
                var requestOptions = {
                    method: 'GET',
                    headers: myHeaders,
                    credentials: 'include'
                };

                return fetch(bonitaServerPath + "/API/system/session/unusedId", requestOptions)
                    .then(response => {
						if (!response.ok) {
							throw Error(response.status);
						}
						return response.json();})
                    .then(body => body.user_id)
                    .catch(error =>  {document.getElementById("error").innerHTML += "<br/> &#x26a0; Unable to retrieve UserId from session. " + error;});
            };

            function updatePassword(authToken) {
                var formData = {"password": document.getElementById("new-password").value}
                var myHeaders = new Headers();
                myHeaders.append("X-Bonita-API-Token", authToken);
                myHeaders.append("Content-Type", 'application/json');

                var requestOptions = {
                    method: 'PUT',
                    headers: myHeaders,
                    credentials: 'include',
                    body: JSON.stringify(formData)
                };


                return getUserId().then(userId =>
                    fetch(bonitaServerPath + "/API/identity/user/" + userId, requestOptions)
                        .then(response => {
							if (!response.ok) {
								throw Error(response.status);
							}
							return response.text();})
                        .then(result =>  {document.getElementById("success").innerHTML = "&#10003; Password updated!"})
                        .catch(error =>  {document.getElementById("error").innerHTML += "<br/> &#x26a0; Unable to update the password. " + error;}));

            };

            function submit() {
				document.getElementById("success").innerHTML = "";
				document.getElementById("error").innerHTML = "";
				bonitaServerPath = document.getElementById("bonita-server-path").value;
				loginToBonita().then(authToken => updatePassword(authToken));
            };
        </script>

    </head>
    <body>
		<div style="display: flex; flex-direction: column;  align-items: center;">
				<h1>CORS Demo, edit user password:</h1>
				<div>
					<label style="width: 150px; display:inline-block;  padding: 5px 0;" for="bonita-server-path">Path to bonita server</label></span>
					<input type="text" placeholder="Enter bonita server path" id="bonita-server-path" required/>
				</div>
				<div>
					<label style="width: 150px; display:inline-block;  padding: 5px 0;" for="username">Username</label></span>
					<input type="text" placeholder="Enter username" id="username" required/>
				</div>
				<div>
					<label style="width: 150px; display:inline-block; padding: 5px 0;" for="username">Current password</label>
					<input type="password" placeholder="Enter current password" id="current-password" required/>
				</div>
				<div>
					<label style="width: 150px; display:inline-block; padding: 5px 0;" for="username">New password</label>
					<input type="password" placeholder="Enter new password" id="new-password" required/>
				</div>
				<button  style="margin: 5px 0;" onclick="submit()">Update password</button>
				<div style="width: 320px;">
					<p style="color:green; padding: 5px 0;" id="success"></p>
					<p style="color:red; padding: 5px 0;" id="error"></p>
				</div>
		</div>
    </body>
</html>
----
