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

# Proxy App

Protect legacy web applications without code changes by deploying the Orchestrator as an identity-aware reverse proxy. It intercepts HTTP traffic, applies authentication policies, and injects identity headers -- so your backend apps receive authenticated user information with zero code modification.

## Overview

The proxy app type is the most common application type in the Orchestrator. It acts as an identity-aware reverse proxy in [HTTP Proxy](/reference/modes/http-proxy) mode, forwarding requests to a backend after applying authentication and authorization policies. Route patterns determine which incoming traffic matches the app, policies enforce authentication at specific URL paths, and headers inject identity attributes into upstream requests.

## How It Works

The HTTP Proxy request flow follows these steps:

1. **Traffic interception** -- The Orchestrator listens on configured route patterns (hostname/path combinations) and intercepts all matching HTTP requests before they reach the upstream application.
2. **Policy evaluation** -- Each request is matched against location-based policies. The Orchestrator determines which authentication and authorization rules apply based on the request path.
3. **User authentication** -- If the user is not authenticated (no valid session), the Orchestrator redirects them to the configured identity provider. After successful authentication, a session cookie is established.
4. **Authorization check** -- The Orchestrator evaluates authorization rules against the authenticated user's attributes. Rules use `and`/`or` conditions with operators like `equals`, `contains`, etc. Unauthorized users are redirected or receive a 403 response.
5. **Header injection** -- For authorized requests, the Orchestrator injects identity headers (e.g., `SM_USER`, `X-Remote-User`) into the upstream request using template syntax (`{{ connector.claim }}`). The upstream application receives these headers and uses them for its own identity decisions.
6. **Proxying and response** -- The request (with injected headers) is forwarded to the upstream backend. The response flows back through the Orchestrator to the client. Optional Service Extensions can modify the request or response at this stage.

## Use Cases

* **Protecting legacy apps without code changes** -- deploy the Orchestrator in front of applications that lack modern authentication support, adding SSO and MFA without modifying application code
* **Header-based authentication injection** -- inject identity headers like `SM_USER`, `REMOTE_USER`, or `X-Remote-User` into upstream requests, enabling SiteMinder-to-modern-IdP migration with zero backend changes
* **Session management for stateless apps** -- the Orchestrator manages user sessions and authentication state on behalf of backend applications that do not track sessions themselves
* **Combining with LDAP Provider for legacy stacks** -- pair proxy apps with an [LDAP attribute provider](/reference/orchestrator/identity-fabric) to load additional user attributes from a directory and inject them as headers, bridging legacy LDAP-dependent applications to modern identity providers

## Key Concepts

### Route Patterns and Upstreams

Route patterns define which traffic the proxy intercepts. Each proxy app maps one or more hostname/path patterns to an upstream backend URL. The Orchestrator acts as a transparent reverse proxy -- the client connects to the Orchestrator, and the Orchestrator forwards to the backend.

### Header Injection

Header injection is the primary mechanism for passing identity to legacy applications. Using `{{ connector.claim }}` template syntax, the Orchestrator maps claims from identity providers into HTTP headers that the upstream application expects. This replaces legacy access management products like SiteMinder without changing the application.

### Header Security on Open Routes

The Orchestrator treats injected identity headers as trusted assertions, so it must never forward a client-supplied copy of a header it manages. How this is enforced depends on whether a route is protected or open.

A route is **open** when its policy sets both `allowUnauthenticated: true` and `allowAll: true`, so unauthenticated requests are proxied straight to the upstream. Open routes are never enabled by default. You must explicitly configure them, and they are intended only for non-sensitive resources such as CSS, images, and login pages.

* **Protected routes** strip any client-supplied copy of a managed header and then inject the Orchestrator's own value, so the upstream never receives a spoofed identity header.
* **Open routes** are proxied without header injection. To prevent spoofing, the Orchestrator strips client-supplied copies of every *statically configured* managed header name -- the names under the app-level `headers:` and every policy's `headers:` -- before proxying. All other client headers are forwarded unchanged.

<Warning>
  Header names produced dynamically by a [`createHeaderSE`](/reference/orchestrator/service-extensions/proxy#createheaderse) are **not** known when configuration loads, so they are **not** stripped on open routes. If you use a `createHeaderSE` to set an identity header -- especially a single global `createHeaderSE` with no static `headers:` entries -- the Orchestrator applies no managed-header filtering on open routes, and the upstream receives whatever header the client sends.

  Never mark a route open if it is sensitive or if the upstream trusts injected identity headers (for example, SiteMinder-style `SM_USER`). Under the shared-responsibility model, choosing which routes are open, and ensuring open routes do not front header-trusting upstreams, is the administrator's responsibility.
</Warning>

### Location-Based Policies

Unlike OIDC and SAML modes where authorization is per-app, HTTP Proxy supports location-based policies. Different URL paths within the same application can have different authentication requirements and authorization rules. For example, `/` might allow all authenticated users while `/admin` requires a specific role.

### Session Management

The Orchestrator manages sessions for proxied applications using cookies. This is critical for applications that lack their own session management. Session configuration (cookie name, lifetime, idle timeout) is defined at the global level and applies to all proxy apps.

### Upstream Login

Some legacy applications have their own login flows in addition to the identity headers. The upstream login feature automates posting credentials to the upstream's login form after the Orchestrator has authenticated the user, handling the "double login" problem.

## Setup

<Tabs>
  <Tab title="Console UI">
    <Steps>
      <Step title="Navigate to Applications">
        Go to **Applications** in the sidebar and click **Create**. Select **Proxied App** from the application type list.
      </Step>

      <Step title="Set the application name">
        Enter a **Name** to identify this application. This name appears in the Console.
      </Step>

      <Step title="Set the upstream URL">
        Enter the **Upstream URL** -- the backend server the Orchestrator forwards requests to after authentication (e.g., `https://internal-hr.example.com`).
      </Step>

      <Step title="Add route patterns">
        Click **Route Pattern** to add URL patterns that map incoming requests to this app. Patterns support hostnames (`example.com`), paths (`/app`), or both (`example.com/app`).
      </Step>

      <Step title="Configure TLS settings">
        Optionally set a **CA Path** for self-signed certificates on the upstream. Enable **Skip TLS Verification** only for testing.
      </Step>

      <Step title="Configure mTLS (optional)">
        For upstream servers requiring client certificate authentication, provide the **TLS Cert** and **TLS Key** file paths or secret provider references.
      </Step>

      <Step title="Set the unauthorized page">
        Optionally enter an **Unauthorized Page** URL where users are redirected when a policy evaluation denies access (e.g., `https://example.com/403`).
      </Step>

      <Step title="Configure preserve host">
        Enable the **Preserve Host** toggle to keep the original Host header on proxied requests. Enable this when the upstream expects the original hostname (e.g., behind another reverse proxy like Apache).
      </Step>

      <Step title="Configure logout settings (optional)">
        Set the **Logout Callback URL** -- the endpoint that facilitates logout for application users. Optionally set the **Post Logout Redirect URL** for where users go after successful logout.
      </Step>

      <Step title="Save">
        Click **Save** to create the application. You can now configure its authentication policies, headers, and identity provider bindings.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Configuration">
    Proxy apps are defined under the `apps` top-level key with `type: proxy`.

    #### Configuration Reference

    | Key                    | Type    | Default | Required | Description                                                                            |
    | ---------------------- | ------- | ------- | -------- | -------------------------------------------------------------------------------------- |
    | `name`                 | string  | --      | Yes      | Unique application identifier                                                          |
    | `type`                 | string  | --      | Yes      | Must be `proxy`                                                                        |
    | `upstream`             | string  | --      | Yes      | Backend URL to forward requests to (e.g., `https://backend:8080`)                      |
    | `routePatterns`        | array   | --      | Yes      | URL patterns to match -- at least one required                                         |
    | `preserveHost`         | boolean | `false` | No       | Preserve the original Host header when proxying to the upstream                        |
    | `tls`                  | string  | --      | No       | Named TLS profile for the upstream connection                                          |
    | `headers`              | array   | --      | No       | Headers to inject on proxied requests                                                  |
    | `policies`             | array   | --      | No       | Location-based authentication and authorization policies                               |
    | `attrProviders`        | array   | --      | No       | Attribute providers for post-authentication attribute loading                          |
    | `unauthorizedPage`     | string  | --      | No       | Default redirect URL for unauthorized requests                                         |
    | `handleUnauthorizedSE` | object  | --      | No       | Service Extension for custom 403 handling (mutually exclusive with `unauthorizedPage`) |
    | `modifyRequestSE`      | object  | --      | No       | Service Extension to modify the request before proxying                                |
    | `modifyResponseSE`     | object  | --      | No       | Service Extension to modify the response before returning to the client                |
    | `logout`               | object  | --      | No       | Logout configuration                                                                   |

    **Route pattern formats:**

    * `"example.com"` -- matches all requests to this hostname
    * `"/path"` -- matches requests to this path on any hostname
    * `"example.com/path"` -- matches requests to this hostname and path

    #### Header Injection

    Headers inject identity attributes into upstream requests using template syntax. This enables legacy applications to receive user information via HTTP headers without supporting modern authentication protocols.

    Each header entry uses either a `name`/`value` pair with template syntax, or a `createHeaderSE` Service Extension. These two approaches are mutually exclusive per header entry.

    | Key                        | Type   | Description                                                                              |
    | -------------------------- | ------ | ---------------------------------------------------------------------------------------- |
    | `headers[].name`           | string | HTTP header name (e.g., `SM_USER`, `X-Remote-User`)                                      |
    | `headers[].value`          | string | Value using `{{ connector.claim }}` template syntax                                      |
    | `headers[].createHeaderSE` | object | Service Extension for dynamic header generation (mutually exclusive with `name`/`value`) |

    **Template syntax:** `{{ connector_name.claim_name }}` references a claim from a named connector. The connector name must match a `connectors[].name` entry, and the claim name must be a valid attribute from that connector.

    <Tabs>
      <Tab title="Console UI">
        In the Maverics Console, headers are configured within each application's settings. An application can define headers that inject identity attributes into upstream requests. The Console supports the `name`/`value` pair approach. Service Extension-based header generation (`createHeaderSE`) is configured via YAML only.
      </Tab>

      <Tab title="Configuration">
        ```yaml theme={null}
        headers:
          - name: SM_USER
            value: "{{ my-idp.email }}"
          - name: X-User-Name
            value: "{{ my-idp.name }}"
          - name: X-User-Given-Name
            value: "{{ my-idp.given_name }}"
          - name: X-User-Family-Name
            value: "{{ my-idp.family_name }}"
          - name: X-Forwarded-User
            value: "{{ my-idp.preferred_username }}"
        ```
      </Tab>
    </Tabs>

    Headers can also be defined at the policy level under `policies[].headers`, which override app-level headers for that specific location.

    <Warning>
      On open routes (`allowUnauthenticated` + `allowAll`), only *statically configured* managed header names are stripped from client input before proxying. Header names emitted dynamically by a `createHeaderSE` are not stripped. See [Header security on open routes](#header-security-on-open-routes).
    </Warning>

    #### Authentication Policies

    Policies control authentication and authorization for matched URL locations. Each policy applies to a specific `location` path within the app's route patterns.

    | Key                                               | Type    | Default | Description                                                                           |
    | ------------------------------------------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
    | `policies[].location`                             | string  | --      | Resource path this policy applies to (required, must be unique per app)               |
    | `policies[].authentication.idps`                  | array   | --      | Connector names for authentication                                                    |
    | `policies[].authentication.allowUnauthenticated`  | boolean | `false` | Allow unauthenticated access to this location                                         |
    | `policies[].authentication.isAuthenticatedSE`     | object  | --      | Service Extension for custom authentication check (must pair with `authenticateSE`)   |
    | `policies[].authentication.authenticateSE`        | object  | --      | Service Extension for custom authentication flow (must pair with `isAuthenticatedSE`) |
    | `policies[].authorization.allowAll`               | boolean | --      | Allow all authenticated users                                                         |
    | `policies[].authorization.rules`                  | array   | --      | Authorization rules with `and`/`or` conditions                                        |
    | `policies[].authorization.rulesAggregationMethod` | string  | --      | How top-level rules combine: `"and"` or `"or"`                                        |
    | `policies[].authorization.isAuthorizedSE`         | object  | --      | Service Extension for custom authorization                                            |
    | `policies[].authorization.unauthorizedPage`       | string  | --      | Redirect URL for unauthorized requests on this location                               |
    | `policies[].authorization.tokenMinting`           | object  | --      | Token minting policy with OPA access token policies                                   |
    | `policies[].decision.lifetime`                    | string  | --      | Decision cache TTL (duration string, e.g., `5m`)                                      |
    | `policies[].headers`                              | array   | --      | Policy-level headers (override app-level headers for this location)                   |
    | `policies[].useQueryParamsMatching`               | boolean | `false` | Enable query parameter matching for this location                                     |

    <Note>
      SE-based authentication (`isAuthenticatedSE` + `authenticateSE`) cannot be mixed with `idps` or `allowUnauthenticated` on the same policy. Use one approach or the other.
    </Note>

    For details on authorization rule operators (`equals`, `notEquals`, `contains`, `notContains`) and nested `and`/`or` conditions, see the [Applications](/reference/orchestrator/applications) reference.

    #### Attribute Providers

    Attribute providers load additional attributes from connectors after authentication. This is useful when you need claims from a second identity source (e.g., loading LDAP attributes after OIDC authentication).

    | Key                               | Type   | Description                                                                                    |
    | --------------------------------- | ------ | ---------------------------------------------------------------------------------------------- |
    | `attrProviders[].name`            | string | Connector name to load attributes from                                                         |
    | `attrProviders[].usernameMapping` | string | Template to map the authenticated user to the connector's lookup key (e.g., `{{ azure.sub }}`) |

    #### Upstream Login

    Upstream login automates authentication to the backend application after the Orchestrator has authenticated the user. This is useful when the upstream application has its own login flow that must be completed.

    | Key                          | Type   | Description                                                                                   |
    | ---------------------------- | ------ | --------------------------------------------------------------------------------------------- |
    | `upstreamLogin.isLoggedInSE` | object | Service Extension to check if the user is logged in to the upstream (required with `loginSE`) |
    | `upstreamLogin.loginSE`      | object | Service Extension to perform the upstream login (required with `isLoggedInSE`)                |

    #### Logout

    | Key                            | Type   | Description                                                     |
    | ------------------------------ | ------ | --------------------------------------------------------------- |
    | `logout.logoutURL`             | string | URL path that triggers logout (required for logout to function) |
    | `logout.postLogoutRedirectURL` | string | URL to redirect the user to after logout                        |

    #### Service Extension Hooks

    The proxy app type supports several Service Extension hooks for custom behavior. Service Extensions are referenced by file path and function name.

    | Hook                   | Purpose                                                                                    |
    | ---------------------- | ------------------------------------------------------------------------------------------ |
    | `modifyRequestSE`      | Modify the HTTP request before it is forwarded to the upstream                             |
    | `modifyResponseSE`     | Modify the HTTP response before it is returned to the client                               |
    | `handleUnauthorizedSE` | Custom handler for 403 unauthorized responses (mutually exclusive with `unauthorizedPage`) |
    | `createHeaderSE`       | Generate headers dynamically (per-header, mutually exclusive with `name`/`value`)          |
    | `loadAttrsSE`          | Custom attribute loading logic                                                             |
    | `isLoggedInSE`         | Check upstream login status (part of `upstreamLogin`)                                      |
    | `loginSE`              | Perform upstream login (part of `upstreamLogin`)                                           |
    | `isAuthenticatedSE`    | Custom authentication check (per-policy, must pair with `authenticateSE`)                  |
    | `authenticateSE`       | Custom authentication flow (per-policy, must pair with `isAuthenticatedSE`)                |
    | `isAuthorizedSE`       | Custom authorization logic (per-policy)                                                    |

    #### Example

    A proxy app protecting a legacy application with header injection, authentication policy, and logout configuration:

    ```yaml theme={null}
    apps:
      - name: legacy-hr-portal
        type: proxy
        upstream: https://internal-hr.example.com
        routePatterns:
          - "hr.example.com"
        preserveHost: true
        policies:
          - location: "/"
            authentication:
              idps: ["azure"]
            authorization:
              rules:
                - or:
                    - contains: ["{{ azure.groups }}", "hr-staff"]
                    - contains: ["{{ azure.groups }}", "hr-managers"]
              rulesAggregationMethod: "or"
            decision:
              lifetime: "5m"
        headers:
          - name: "X-Remote-User"
            value: "{{ azure.email }}"
          - name: "X-Remote-Name"
            value: "{{ azure.name }}"
          - name: "X-Remote-Groups"
            value: "{{ azure.groups }}"
        attrProviders:
          - name: "corporate-ldap"
            usernameMapping: "{{ azure.sub }}"
        logout:
          logoutURL: /logout
          postLogoutRedirectURL: https://hr.example.com/
    ```
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Headers not arriving at the upstream application">
    **Symptoms:** The upstream application does not see injected headers. User identity is missing from the request.

    **Causes:**

    * Header template syntax error -- missing `{{ }}` delimiters or incorrect spacing.
    * Connector name typo in the template (e.g., `{{ my_idp.email }}` when the connector is named `my-idp`).
    * The upstream application or an intermediate proxy is stripping custom or `X-` headers.

    **Resolution:**

    * Verify template syntax uses the `{{ connector.attribute }}` format exactly, with double curly braces and a dot separator.
    * Confirm the connector name in the template matches a `connectors[].name` entry in your configuration.
    * Check whether the upstream application or any intermediate load balancer strips custom headers. Some application servers discard headers with underscores or specific prefixes by default.
  </Accordion>

  <Accordion title="Authentication redirect loop">
    **Symptoms:** The browser loops endlessly between the Orchestrator and the identity provider, never completing authentication.

    **Causes:**

    * Session cookie domain mismatch -- the cookie is set for a domain that does not cover the Orchestrator's hostname.
    * The IdP callback URL does not match the `oauthLoginRedirect.urls` configured on the connector.
    * The `routePatterns` do not cover the callback path, so the Orchestrator does not intercept the callback.

    **Resolution:**

    * Verify `session.cookie.domain` covers the Orchestrator's hostname (e.g., `.example.com` for `app.example.com`).
    * Ensure the callback URL registered at the IdP is listed in `oauthLoginRedirect.urls` on the connector.
    * Check that `routePatterns` include the callback path so the Orchestrator can process the authentication response.
  </Accordion>

  <Accordion title="403 Forbidden for authorized users">
    **Symptoms:** The user authenticates successfully but receives a 403 response on specific paths.

    **Causes:**

    * Location-based policy mismatch -- the `location` path in the policy does not match the actual request path.
    * Authorization rules reference a connector attribute that does not return the expected value.
    * `rulesAggregationMethod` is set to `and` when `or` is needed (all rules must pass vs. any rule).

    **Resolution:**

    * Verify the `location` path in the policy matches the request path. Location matching is prefix-based.
    * Check that the `connector.attribute` references in rules return expected values by inspecting the connector's claims.
    * Review the `rulesAggregationMethod` setting -- use `or` if any single rule should grant access, or `and` if all rules must pass.
  </Accordion>

  <Accordion title="Upstream connection refused or timeout">
    **Symptoms:** 502 Bad Gateway or connection timeout after the user successfully authenticates.

    **Causes:**

    * The `upstream` URL is unreachable from the Orchestrator host.
    * TLS certificate mismatch on the upstream -- the Orchestrator cannot verify the upstream's certificate.
    * Wrong port in the `upstream` URL.

    **Resolution:**

    * Verify the Orchestrator can reach the upstream URL from its host (test with `curl` or equivalent).
    * If using HTTPS for the upstream, check the TLS profile configuration and ensure the upstream's certificate is trusted.
    * Confirm the port in the `upstream` URL matches the port the backend application is listening on.
  </Accordion>

  <Accordion title="Session expires too quickly">
    **Symptoms:** Users are forced to re-authenticate frequently, even during active use.

    **Causes:**

    * `session.lifetime.idleTimeout` or `maxTimeout` is set too low for the expected user activity pattern.
    * A load balancer in front of multiple Orchestrator instances is not using sticky sessions, causing requests to hit instances that do not have the user's session.

    **Resolution:**

    * Adjust `session.lifetime.idleTimeout` and `session.lifetime.maxTimeout` to match your expected user session duration.
    * Configure sticky sessions on the load balancer using the `maverics_session` cookie so that requests from the same user are routed to the same Orchestrator instance.
  </Accordion>
</AccordionGroup>

## Related Pages

<CardGroup cols={2}>
  <Card title="Applications" icon="grid-2" href="/reference/orchestrator/applications">
    Overview of application types, route patterns, and shared configuration
  </Card>

  <Card title="HTTP Proxy" icon="server" href="/reference/modes/http-proxy">
    HTTP Proxy mode configuration for proxy app deployments
  </Card>

  <Card title="Identity Fabric" icon="compass" href="/reference/orchestrator/identity-fabric">
    Configure the identity providers referenced in app policies
  </Card>

  <Card title="Authorization" icon="scale-balanced" href="/reference/orchestrator/authorization">
    Authorization rules, operators, and rule aggregation
  </Card>

  <Card title="Service Extensions" icon="puzzle-piece" href="/reference/orchestrator/service-extensions">
    Custom request/response modification via Service Extensions
  </Card>

  <Card title="Transport Layer Security (TLS)" icon="shield-halved" href="/reference/orchestrator/tls-security">
    Named TLS profiles for upstream connections
  </Card>
</CardGroup>
