> ## 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.

# WebAdmin Security & Hardening

This page covers what you must configure to run ASEE Flow WebAdmin and its engine REST API safely in production. It builds on the [Authentication](/webadmin/authentication) and [Deployment](/webadmin/deployment) pages.

<Warning>
  The demo ships deliberately permissive defaults for local development — a public client secret, open CORS (`*`), no audience validation, and (optionally) the no-auth `none` mode. **Every one of these must be overridden before production.** The checklist at the end of this page lists them.
</Warning>

## Choose a production authentication mode

| Mode | Production? | Notes |
| - | - | - |
| `basic` | ✅ | HTTP Basic; credentials travel in every request, so **require HTTPS**. Good for headless/API clients. |
| `form` | ✅ Spring Boot and Run | Session-cookie login for the browser. Since 1.0.2 the REST API also accepts HTTP Basic, so programs can call it. Not with a protected Engine REST WAR on Tomcat or WildFly — use `basic` or `oauth2` there; see [Production hardening](/installation/production-hardening#tomcat-and-wildfly). |
| `oauth2` | ✅ | Standard OIDC (Okta, Entra ID, Keycloak, custom). Supports Bearer tokens for external clients when the resource server is configured (below). |
| `keycloak` | ✅ | OIDC plus live Keycloak directory integration (users, groups, tenants). Best for multi-tenant setups. Not in the WebAdmin WAR, which doesn't include the Keycloak identity plugin — use `oauth2` there. |
| `none` | ❌ never | No authentication — installs a synthetic `demo` user and permits all requests. Development only. |

`none` is for local development only. As a safeguard, the starter refuses to start in `none` mode unless a development Spring profile (`dev`, `demo`, `test`, or `local`) is active — so it cannot be silently left enabled in production. For any real deployment, use `form`, `basic`, `oauth2` or `keycloak` — on Tomcat and WildFly with a protected Engine REST WAR, `basic` or `oauth2`.

## Never leave the engine REST API unprotected

`aseeflow.webadmin.disable-rest-security` removes `/engine-rest/**` from WebAdmin's security chain. Its safety depends on the deployment:

* **Spring Boot (in-process engine)** — there is no other security layer. Setting `disable-rest-security: true` leaves `/engine-rest/**` **fully open**: every request returns `200`, even with wrong credentials. **Keep it `false`.**
* **WAR** — the separate Engine REST WAR has to enforce its own authentication, so `disable-rest-security: true` is the correct setting there (WebAdmin must not try to secure endpoints it doesn't contain). But the Tomcat and WildFly distributions ship that WAR with its authentication filter commented out: turn it on as described in [Production hardening](/installation/production-hardening#tomcat-and-wildfly). See also [Deployment](/webadmin/deployment).

If you ever disable REST security in a Spring Boot deployment, you must protect `/engine-rest` with a separate layer (reverse-proxy auth, mTLS, or network policy).

Also enable the engine's authorization framework in production so the engine enforces per-user permissions:

```yaml theme={null}
camunda.bpm:
  authorization:
    enabled: true
```

## Secure the engine REST API (OIDC modes)

For `oauth2` and `keycloak`, Bearer-token acceptance on `/engine-rest` is **off by default** and activates only when you set an issuer:

```yaml theme={null}
spring.security.oauth2.resourceserver.jwt:
  issuer-uri: ${keycloak.url.auth}/realms/aseeflow
  audiences:
    - account
```

<Warning>
  **Set `audiences` in production.** With an empty audience list, only the issuer is validated — in a multi-tenant Keycloak realm, *any* token the realm signs is accepted, which is a privilege-escalation hole. List the audience your tokens actually carry.
</Warning>

Keycloak access tokens carry `aud: "account"` by default, not your client ID. To validate against the client ID instead, add an **Audience** mapper on the client in Keycloak (Clients → *client* → Client scopes → *dedicated* → Add mapper → Audience), then list that client ID in `audiences`.

The REST chain always accepts the SSO **session cookie**; configuring the resource server makes it accept **both** a session and a Bearer token. There is no token-only/session-free REST surface.

## Map identities correctly

Each login bridges the authenticated principal into the engine as a user ID plus resolved groups and tenants. Two misconfigurations cause silent lockouts:

* **User ID mismatch.** The `user-name-attribute` claim (e.g. `preferred_username`, `sub`, `email`) becomes the engine user ID. If authorizations were assigned to a username but the token maps a UUID, the user logs in but resolves to **zero groups and zero tenants** — every query comes back empty. Make sure the claim matches the IDs the engine knows.
* **Missing group claim.** In `oauth2` mode the engine reads groups from a token claim (`camunda.bpm.oauth2.identity-provider.group-name-attribute`, default `groups`). If your provider doesn't emit that claim, add a groups mapper on the provider. In `keycloak` mode the identity provider plugin queries Keycloak directly, so token group claims aren't used.

## Manage secrets

* **Never hard-code the OIDC client secret.** Inject it via an environment variable (`KEYCLOAK_CLIENT_SECRET`) and keep the YAML referencing `${KEYCLOAK_CLIENT_SECRET:...}`.
* The demo's fallback client secret and the `demo` / `demo` user are **public and demo-only**. Rotate the secret in your identity provider and create real users before production.
* Never commit secrets, database passwords, or TLS keys. If one leaks into git history, rotate it immediately.

## Transport security

* **Serve WebAdmin over HTTPS.** Basic and form modes send credentials or session cookies that must not travel in clear text.
* **`disableSSLCertificateValidation` is development-only.** The demo sets it `true` for a local self-signed Keycloak; in production remove it (or set `false`) and use a CA-trusted certificate on your issuer.
* **Behind a reverse proxy / load balancer:** terminate HTTPS at the proxy and forward auth headers transparently. Register Keycloak redirect URIs against the **external** HTTPS URL (e.g. `https://webadmin.example.com/webadmin/login/oauth2/code/keycloak`), not the internal one, or OIDC login redirects break.
* For session-based modes, set the session cookie to `SameSite=Lax` as a baseline CSRF defense:

  ```yaml theme={null}
  server.servlet.session.cookie.same-site: lax
  ```

## Restrict CORS

CORS only matters when a cross-origin browser client (not the bundled UI) calls the REST API — relevant in `oauth2` mode. The default is open (`["*"]`); restrict it to your real origins:

```yaml theme={null}
aseeflow:
  webadmin:
    cors:
      allowed-origins:
        - https://admin.example.com
```

## Production hardening checklist

* [ ] `aseeflow.webadmin.authentication` is never `none` (and no development Spring profile is active).
* [ ] Spring Boot deployment: `disable-rest-security` is `false`.
* [ ] Tomcat / WildFly: the Engine REST WAR's authentication is on, and WebAdmin runs in `basic` or `oauth2` through its proxy — see [Production hardening](/installation/production-hardening).
* [ ] `camunda.bpm.authorization.enabled: true`.
* [ ] OIDC with external clients: `resourceserver.jwt.issuer-uri` **and** `audiences` are set.
* [ ] OIDC client secret supplied via environment variable; demo secret and demo user removed.
* [ ] HTTPS everywhere; `disableSSLCertificateValidation` not `true`; issuer certificate trusted.
* [ ] Keycloak redirect URIs point to the external HTTPS URL.
* [ ] `oauth2` mode: `cors.allowed-origins` restricted to real origins (not `*`).
* [ ] Identity mapping tested — groups and tenants resolve (not empty).

## Verify your configuration

Confirm endpoints are actually protected. Unauthenticated requests must be rejected:

```bash theme={null}
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
  "http://localhost:8080/engine-rest/process-definition/count"
# Expected: 401 (or a 302 login redirect in form mode) — never 200
```

Valid credentials succeed, wrong credentials fail (basic mode shown):

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

For OIDC modes, fetch a token and call REST with it:

```bash theme={null}
TOKEN=$(curl -s -X POST \
  "http://localhost:9000/auth/realms/aseeflow/protocol/openid-connect/token" \
  -d grant_type=password -d client_id=aseeflow-identity-service \
  -d "client_secret=$KEYCLOAK_CLIENT_SECRET" \
  -d username=demo -d password=demo | jq -r .access_token)

curl -s -o /dev/null -w "HTTP %{http_code}\n" \
  -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/engine-rest/process-definition/count"   # 200
```

Check that identities resolve — an authenticated user should see their groups:

```bash theme={null}
curl -s -u demo:demo "http://localhost:8080/engine-rest/group?maxResults=50"
# Empty result usually means an identity-mapping problem (see "Map identities correctly").
```


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