> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aseeflow.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Production hardening

The ASEE Flow distributions — Run, Tomcat and WildFly — and the Docker images built from them ship with defaults for trying ASEE Flow out, inherited from Camunda 7: an engine REST API that anyone can use on Tomcat and WildFly, `demo` users with known passwords, the invoice example, and development settings in Run. This page lists what to change before you run a distribution in production. It applies to ASEE Flow 1.0.2.

<Warning>
  On Tomcat and WildFly the engine REST API (`/engine-rest`) ships **without authentication**. Anyone who can reach the server can deploy processes, start and delete instances, and create administrators. Protect it before the server is reachable from anywhere but your own machine — see [Tomcat and WildFly](#tomcat-and-wildfly).
</Warning>

## Before you start

* Use JDK 17 or 21 — see [Supported Environments](/introduction/supported-environments#java). Newer JDKs are not supported on the 1.x line; on Java 25 its JavaScript engine fails.
* Plan for HTTPS. The setups below use HTTP Basic, which sends the password with every request. Terminate TLS at the server or at a reverse proxy in front of it.
* Use a production database instead of the embedded H2 — see [Database Schema](/installation/database-schema).

## Remove the demo users

The invoice example creates the users `demo`, `john`, `mary` and `peter`, each with the user name as password, and makes `demo` an administrator. Run's development configuration creates `demo` / `demo` as well.

1. Remove the invoice example before the first start:
   * Tomcat: `server/apache-tomcat-<version>/webapps/aseeflow-invoice/`
   * WildFly: `server/wildfly-<version>/standalone/deployments/aseeflow-example-invoice-jakarta-<version>.war`
   * Run: start with `--production` ([below](#aseeflow-run)), which leaves the example out.
2. Create your first administrator on the setup page at `/aseeflow/app/admin/default/setup/`. The page works only while no administrator exists. With LDAP or Keycloak as identity provider, your administrators come from there instead.
3. If the example has already run, delete its users in **Admin** → **Users**.

## ASEE Flow Run

Start Run with its production configuration:

```bash theme={null}
./start.sh --production      # start.bat --production on Windows
```

`configuration/production.yml` turns on authorization checks and the password policy, leaves out the invoice example and the WADL, creates no demo user, and serves HTTPS on port 8443.

Then change two things in `production.yml`:

1. **Replace the bundled keystore.** `server.ssl` points at the demo `keystore.p12` with the password `camunda`. Configure your own certificate.
2. **Let WebAdmin protect the REST API.** Run loads WebAdmin by default, and WebAdmin protects `/engine-rest` itself: the browser uses the WebAdmin login session, and programs send HTTP Basic credentials. Run's own REST authentication accepts only HTTP Basic, so together with WebAdmin's login page it makes every WebAdmin call fail. Turn it off while WebAdmin is loaded:

   ```yaml theme={null}
   camunda.bpm:
     run:
       auth.enabled: false
   ```

   If you start Run without WebAdmin, keep `auth.enabled: true` — then nothing else protects the REST API.

The production configuration leaves CORS off. If a browser application on another origin must call the REST API, list its origin rather than `*`. [ASEE Flow Run](/user-guide/aseeflow-bpm-run) describes the remaining options.

## Tomcat and WildFly

On these distributions the engine belongs to the server, and the REST API is a separate application: the Engine REST WAR. WebAdmin's login protects WebAdmin's own pages, but the data they show comes from the Engine REST WAR. So you protect the Engine REST WAR, and WebAdmin's login mode follows how you protected it:

| Engine REST WAR protected with | WebAdmin login mode |
| - | - |
| HTTP Basic — [described below](#protect-the-engine-rest-war) | `basic`, through WebAdmin's proxy |
| Single sign-on tokens — [described below](#single-sign-on-instead-of-http-basic) | `oauth2`, through WebAdmin's proxy |

<Note>
  WebAdmin's `form` login does not work with a protected Engine REST WAR. The Engine REST WAR doesn't know the WebAdmin session, so the browser asks for a second login in its own dialog — and that login stays active after you log out of WebAdmin, until the browser is closed. Keep `form` for trying ASEE Flow out with the default, unprotected setup.
</Note>

### Protect the Engine REST WAR

The Engine REST WAR's `WEB-INF/web.xml` contains a commented-out HTTP Basic filter. Remove the comment markers around it, so that it reads:

```xml theme={null}
<filter>
  <filter-name>camunda-auth</filter-name>
  <filter-class>org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter</filter-class>
  <async-supported>true</async-supported>
  <init-param>
    <param-name>authentication-provider</param-name>
    <param-value>org.camunda.bpm.engine.rest.security.auth.impl.HttpBasicAuthenticationProvider</param-value>
  </init-param>
  <init-param>
    <param-name>rest-url-pattern-prefix</param-name>
    <param-value></param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>camunda-auth</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
```

Where you find the file:

* **Tomcat** — `server/apache-tomcat-<version>/webapps/engine-rest/WEB-INF/web.xml`. Edit it in place and restart Tomcat.
* **WildFly** — inside the archive `server/wildfly-<version>/standalone/deployments/aseeflow-engine-rest-jakarta-<version>.war`. With WildFly stopped, take the file out in a working directory, edit it, and put it back:

  ```bash theme={null}
  jar xf <deployments>/aseeflow-engine-rest-jakarta-<version>.war WEB-INF/web.xml
  # edit WEB-INF/web.xml as shown above
  jar uf <deployments>/aseeflow-engine-rest-jakarta-<version>.war WEB-INF/web.xml
  ```

The engine's users then sign in with their own credentials, and their authorizations apply. Only the engine list, `/engine-rest/engine`, stays readable without a login. Check the result:

```bash theme={null}
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/engine-rest/process-definition/count
# 401
curl -s -o /dev/null -w "%{http_code}\n" -u <user>:<password> http://localhost:8080/engine-rest/process-definition/count
# 200
```

### Switch WebAdmin to basic login through its proxy

Set three WebAdmin properties:

```properties theme={null}
aseeflow.webadmin.authentication=basic
aseeflow.webadmin.engine-rest-proxy-enabled=true
aseeflow.webadmin.engine-rest-client-url=api/engine-rest
```

* **Tomcat** — add them to the `CATALINA_OPTS` line in `bin/setenv.sh` (`setenv.bat` on Windows). The distribution's `setenv` sets `CATALINA_OPTS` itself, so a value you set before starting Tomcat is overwritten:

  ```bash theme={null}
  export CATALINA_OPTS="-Xmx512m -Daseeflow.webadmin.authentication=basic -Daseeflow.webadmin.engine-rest-proxy-enabled=true -Daseeflow.webadmin.engine-rest-client-url=api/engine-rest"
  ```

* **WildFly** — append them as `-D` options to `JAVA_OPTS` in `bin/standalone.conf` (`standalone.conf.bat` on Windows), or pass them to `standalone.sh`.

The browser then asks once for the user name and password, in its own dialog. WebAdmin and the REST calls behind it use the same credentials, and the user's own authorizations apply. The proxy is what makes this a single login: without it, the browser asks a second time for `/engine-rest`. Basic login has no reliable logout — close the browser after logging out, especially on a shared computer.

### Single sign-on instead of HTTP Basic

The Engine REST WAR can accept access tokens from an OpenID Connect provider, such as Keycloak, instead of HTTP Basic. WebAdmin then signs users in at that provider: they sign in once, on the provider's page, and WebAdmin's proxy sends their access token with every call to the REST API.

At the provider, WebAdmin needs a confidential client with the authorization-code flow. Allow it to redirect to `https://<your host>/webadmin/login/oauth2/code/keycloak` after sign-in, and to `https://<your host>/webadmin` after logout.

**1. Protect the Engine REST WAR with tokens.** In the same `WEB-INF/web.xml` as [above](#protect-the-engine-rest-war), the blocks marked "uncomment to enable" — two in WildFly's file, three in Tomcat's — set up token validation. Remove their comment markers, leaving the blocks where they are, so that they read:

```xml theme={null}
<context-param>
  <param-name>contextClass</param-name>
  <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>org.aseeflow.bpm.engine.rest.security.EngineRestSecurityConfig</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
```

and further down:

```xml theme={null}
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  <async-supported>true</async-supported>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
  <filter-name>camunda-auth</filter-name>
  <filter-class>org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter</filter-class>
  <async-supported>true</async-supported>
  <init-param>
    <param-name>authentication-provider</param-name>
    <param-value>org.camunda.bpm.engine.rest.security.auth.impl.ContainerBasedAuthenticationProvider</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>camunda-auth</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
```

Leave the HTTP Basic filter commented out: both filters are named `camunda-auth`.

**2. Name the provider and the audience.** The Engine REST WAR takes both from system properties. Add them as `-D` options where you put WebAdmin's settings — the `CATALINA_OPTS` line in Tomcat's `bin/setenv`, `JAVA_OPTS` in WildFly's `bin/standalone.conf`:

```bash theme={null}
-Dorg.camunda.bpm.engine.rest.security.oauth2.issuer-uri=https://<provider>/realms/<realm>
-Dorg.camunda.bpm.engine.rest.security.oauth2.audiences=aseeflow-engine-rest
```

The issuer, the signature and the expiry show only that the realm issued a token, not for which application. With `audiences`, a token must also name the REST API in its `aud` claim. Choose that name — here `aseeflow-engine-rest` — and have the provider add it to the access tokens of WebAdmin's client and of every other application that may call the REST API. In Keycloak, that is an **Audience** mapper in the client's dedicated client scope, with *Included Custom Audience* set to the name and *Add to access token* on. List several names separated by commas. Without `audiences`, the Engine REST WAR accepts any valid token of the realm, whichever application it was issued to.

The token's `preferred_username` claim names the engine user (another claim: `org.camunda.bpm.engine.rest.security.oauth2.user-name-attribute`). That user's groups and authorizations come from the engine's own user management, not from the provider's groups — create the engine users under the same names and give them their authorizations. Programs call the REST API with an access token of their own; the same rule applies to the user it names.

**3. Switch WebAdmin to `oauth2` through its proxy.** Put WebAdmin's settings in an `application.yml`, in a directory of your choice:

```yaml theme={null}
aseeflow:
  webadmin:
    authentication: oauth2
    engine-rest-proxy-enabled: true
    engine-rest-client-url: api/engine-rest

camunda.bpm.oauth2:
  sso-logout:
    enabled: true
    postLogoutRedirectUri: https://<your host>/webadmin

spring.security.oauth2.client:
  registration:
    keycloak:
      provider: keycloak
      client-id: <client id>
      client-secret: ${OIDC_CLIENT_SECRET}
      authorization-grant-type: authorization_code
      redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
      scope: openid, profile, email
  provider:
    keycloak:
      issuer-uri: https://<provider>/realms/<realm>
      user-name-attribute: preferred_username
```

`${OIDC_CLIENT_SECRET}` keeps the client secret out of the file: set that environment variable for the server. Then point WebAdmin to the directory with one more `-D` option, next to the issuer:

```bash theme={null}
-Dspring.config.additional-location=file:/opt/aseeflow/webadmin-config/
```

The trailing `/` marks a directory, and the file in it must be named `application.yml`. The option applies to every Spring Boot application on the server; on the distributions that is WebAdmin alone. [OAuth2 / OIDC](/webadmin/authentication/oauth2) describes the properties.

Use `oauth2` also when the provider is Keycloak. The `keycloak` mode needs the Keycloak identity plugin, which the WebAdmin WAR doesn't include, so the WAR doesn't start in that mode.

**Start the provider first.** The Engine REST WAR and WebAdmin read the provider's configuration when they start. If the provider can't be reached then, neither of them starts, and `/engine-rest` and `/webadmin` answer `404`. Restart the server once the provider is reachable — on WildFly, redeploying the two applications is enough.

Check the result:

```bash theme={null}
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/engine-rest/process-definition/count
# 401
curl -s -o /dev/null -w "%{http_code}\n" -u <user>:<password> http://localhost:8080/engine-rest/process-definition/count
# 401 - HTTP Basic no longer gets in
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer <access token>" http://localhost:8080/engine-rest/process-definition/count
# 200
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer <a token without the audience>" http://localhost:8080/engine-rest/process-definition/count
# 401
```

Then open WebAdmin: the browser goes to the provider's sign-in page once, and WebAdmin's data loads. WebAdmin renews the access token while the user works. If the provider has ended the user's session, the renewal fails, WebAdmin ends its own session too, and the user signs in again. Logging out of WebAdmin also ends the session at the provider.

### Harden the server

**Tomcat** — in `server/apache-tomcat-<version>/`:

* Remove `webapps/manager`, `webapps/host-manager`, `webapps/examples`, `webapps/docs` and `webapps/ROOT`. They accept requests only from the same machine, but a reverse proxy on that machine counts as the same machine.
* Hide server details on error pages: add `<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false"/>` inside `<Host>` in `conf/server.xml`.
* Turn off hot deployment: set `autoDeploy="false"` on `<Host>`.
* Disable the shutdown port: `<Server port="-1" ...>`. Stop Tomcat through its process or service manager afterwards, because `shutdown-aseeflow.bat` and `shutdown.sh` use that port.

**WildFly** — in `server/wildfly-<version>/standalone/configuration/standalone.xml`:

* Keep the management interface on `127.0.0.1`, as the distribution configures it, and turn off its web console: `console-enabled="false"` on `<http-interface>`.
* Turn off periodic deployment scanning: `scan-interval="0"` on `<deployment-scanner>`. Deployments are still read at startup.
* Replace the `https` listener's certificate, which WildFly generates for `localhost`, with your own — or remove the listener if TLS ends at a reverse proxy.
* Remove the welcome page: the `<location name="/" handler="welcome-content"/>` entry.

On both servers you can also remove the ASEE Flow welcome page: `webapps/aseeflow-welcome` on Tomcat, `deployments/aseeflow-welcome.war` on WildFly.

## Docker images

The [Docker images](/installation/docker) are built from these distributions and carry the same defaults. Apply the same changes through mounted configuration files or an image derived from ours. Two things differ:

* The WildFly image binds the management interface to all network interfaces. Don't publish port `9990`, and restrict access to it inside your network, for example with a Kubernetes network policy.
* Without database settings, the images fall back to an embedded H2 database. Always configure your database.

## Your own applications

Protecting the Engine REST WAR doesn't restrict how your own applications sign in their users:

* A process application on the server's engine uses the Java API, not REST.
* A back-end that calls the REST API sends a technical user's credentials with HTTP Basic, and keeps its own login — form-based or any other.
* A browser front-end should call its own back-end rather than the REST API directly. Calling the Engine REST WAR from the browser leads to the same second login as WebAdmin's `form` mode.

## Checklist

* [ ] JDK 17 or 21.
* [ ] A production database, not H2.
* [ ] HTTPS, at the server or a reverse proxy.
* [ ] Invoice example removed, demo users deleted, first administrator created.
* [ ] Run: started with `--production`, own keystore, Run's REST authentication off while WebAdmin is loaded.
* [ ] Tomcat and WildFly: Engine REST WAR protected — an anonymous call returns `401`.
* [ ] Tomcat and WildFly: WebAdmin in `basic` or `oauth2` mode, through its proxy.
* [ ] Tomcat and WildFly with single sign-on: the Engine REST WAR's `audiences` set, and that audience in the tokens of the applications that may call it.
* [ ] Tomcat and WildFly: server hardening applied.
* [ ] Docker: WildFly's port `9990` not published.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.