> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/gateway/inject-identity.md).

# Give the application the authenticated identity

Give an application behind the Access Gateway the authenticated identity — as request headers, query parameters or cookies, or by form fill: filling and posting the application's own login form, the c

## What this is, and when you need it

The gateway has already decided who the user is by the time a request reaches the application. This is how you tell the application. **Injection** attaches identity to the upstream request as headers, query parameters or cookies; **form fill** submits the application's own login form on the user's behalf when it has a login it will not give up. Both are **metadata** on a URI pattern, and that pattern is what scopes them. Finish [Protect an application behind the Access Gateway](/administration-guide/gateway/protect-an-application.md) first.

## At a glance

|                          |                                                                                                                                                                             |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | An application that receives the authenticated user's identity in the form it expects, on the paths you named and no others                                                 |
| **Where**                | **Metadata** on a URI pattern, `/webconsole/editProviderPattern?id=<pattern-id>&providerId=<id>`                                                                            |
| **You need first**       | A working URI pattern; the exact header, parameter, cookie or form-field names the application reads; a test user with an identity on the content provider's managed system |
| **Takes**                | 30–45 minutes, plus five minutes of cache wait per change                                                                                                                   |
| **Reversible**           | Yes — delete the metadata entry. An identity header that already reached an application is not recallable, so treat a wrong one as a disclosure                             |

### Which mechanism do you need?

| The application…                                                | Use                                                                       |
| --------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Reads identity from a request header, query parameter or cookie | [Header, parameter and cookie injection](#header-and-parameter-injection) |
| Has its own login form and no way to turn it off                | [Form fill](#form-fill)                                                   |
| Has a CAPTCHA or a script-signed login form                     | Neither works — see the warning under form fill                           |

{% hint style="warning" %}
**Injection is only as trustworthy as your exclude rules.** A header you inject can also be sent by a browser. For every identity header, parameter or cookie you set, add an **Exclude Headers** or **Exclude Cookies** entry naming the same thing on the same pattern, so the inbound copy is stripped before the trusted one is set. This is the single most commonly missed step in a gateway deployment.
{% endhint %}

## Header and parameter injection

Injection is configured as **metadata** on a URI pattern, or on one HTTP method of that pattern. Six metadata types exist — four that inject (**Set Header**, **Set URI String Parameter**, **Set Cookie**, **Form Post Processor**) and two that suppress (**Exclude Headers**, **Exclude Cookies**). If you need a seventh kind of injection the answer is a Groovy script value, not a new type. All six types, the five value sources and the two propagation checkboxes are in [Access Gateway reference](/administration-guide/gateway/access-gateway-reference.md#metadata-value-sources).

### Worked example: inject the principal and a group claim

An intranet reporting tool published at `legacy.example.com/app` reads `SM_USER` for identity, reads `SM_GROUPS` to decide which reports a user may run, and needs a fixed tenant header on every request. It has no login of its own. This is the whole configuration, on the one URI pattern `/app/*`.

The group claim is the interesting half. **Value from User Profile** offers six attributes and group membership is not one of them, so the group list comes from a Groovy script — which is the general answer whenever the value you need is not one of the six.

{% stepper %}
{% step %}

### Write the group-claim script in the Groovy Manager

Create a script that extends the URI-federation processor and returns the string you want in the header. Anything OpenIAM can reach is available through `context.getBean(...)`; the script below reads the signed-in user's groups and returns them comma-separated, and returns `null` — meaning "send nothing" — for an anonymous request.

{% code title="AM/GroupClaimHeader\_1.0.groovy" overflow="wrap" lineNumbers="true" expandable="true" %}

```groovy
import org.openiam.am.srvc.dto.AbstractPatternMetaValue
import org.openiam.am.srvc.groovy.URIFederationGroovyProcessor
import org.openiam.base.request.AdaptiveAuthenticationExtraDataRequest
import org.openiam.common.beans.mq.AuthManagerMQService
import org.openiam.dto.ContentProvider
import org.openiam.dto.URIPattern

class GroupClaimHeader extends URIFederationGroovyProcessor {

    @Override
    String getValue(String userId, ContentProvider contentProvider, URIPattern pattern,
                    AbstractPatternMetaValue metaValue, URI uri,
                    AdaptiveAuthenticationExtraDataRequest params) {
        if (!userId) {
            return null
        }
        def authManager = context.getBean(AuthManagerMQService.class)
        def names = authManager.getGroupsForUser(userId)
                               .collect { it.entity?.name }
                               .findAll { it }
                               .sort()
        return names ? names.join(',') : null
    }
}
```

{% endcode %}

The script runs on **every matching request**, so keep it to one lookup and no I/O of its own. A slow script here is a slow application: it executes inside the authorization call the proxy is waiting on.
{% endstep %}

{% step %}

### Add the injection metadata

Open the URI pattern `/app/*`, scroll to **Metadata** and click **Add**.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-fdd77cefeb940c20bd72c074baa380c774f672b6%2Fgateway-inject-identity-01-metadata-section.png?alt=media" alt="The Meta Data section of a URI pattern, empty, with the add button at the right of its toolbar"><figcaption></figcaption></figure>

In the dialog, set **Property Name** to `identity-headers` — this is the name of the metadata entry itself, not of a header, and it exists so you can find the entry again — and set **Metadata Type** to **Set Header**.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-47a6c32197a3772b61a152426b8882f2f630115f%2Fgateway-inject-identity-02-set-header.png?alt=media" alt="The Metadata dialog with Property Name set to identity-headers and the Metadata Type list open, showing Set Header among its six options"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add one metadata attribute per header

Inside the same dialog, **Metadata Attributes** is the list of headers this entry sets. Add three, each with its own **Property Name**, **Property Type** and **Property Value**:

| Property Name | Property Type           | Property Value                                                                                                                                                 |
| ------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SM_USER`     | Value from User Profile | **Principal**                                                                                                                                                  |
| `SM_GROUPS`   | Groovy Script           | the script you just wrote — choosing **Groovy Script** replaces the value box with a script picker, which lists scripts as `AM/GroupClaimHeader.groovy (v1.0)` |
| `X-Tenant-Id` | Static Value            | `NORTHWIND`                                                                                                                                                    |

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-490abece4ee4a3b9d008d86cb14b8993e88d5cc4%2Fgateway-inject-identity-03-static-value.png?alt=media" alt="The Edit Attribute dialog with Property Name set to X-Tenant-Id and the Property Type list open, showing Static Value among its options"><figcaption></figcaption></figure>

Leave **Propagate Through Proxy** clear on all three: these are claims for the application, and there is no reason for the browser to see them in the response. Leave **Propagate on Error** clear as well, so a failed request does not carry identity to an error page. Set both deliberately rather than trusting a default — the dialog opens with both boxes clear, older OpenIAM documentation describes the opposite, and the way to be sure is to save, reopen the value and read them back.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-8d93266ee5b6ee5fff21dbd52bc23c445f449b0f%2Fgateway-inject-identity-04-attribute-complete.png?alt=media" alt="The completed Edit Attribute dialog: X-Tenant-Id, Static Value, value NORTHWIND, with Propagate Through Proxy and Propagate on Error both left clear"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the matching Exclude Headers entry

Add a second metadata entry on the same pattern: **Property Name** `strip-spoofed-identity`, **Metadata Type** **Exclude Headers**, with one attribute per header name you inject — `SM_USER`, `SM_GROUPS`, `X-Tenant-Id` — each with **Property Type** **Empty Value**.

This is not optional. `SM_USER` is an ordinary request header, and any browser can send one. Without the exclude entry the application may receive both the browser's copy and yours, and which one it reads is the application's business, not OpenIAM's — an application that trusts a header for identity and receives two of them is an authentication bypass waiting to be found.
{% endstep %}

{% step %}

### Save, wait, and check what the application actually received

Save the pattern. The confirmation reads *"URI Pattern for Content Provider was saved successfully. Please allow a few minutes for changes to propagate."* — take it literally.

Then prove it from the application's side, not from OpenIAM's. Request a page under `/app/` as a real user and read the headers the application logged, or point the pattern temporarily at any endpoint that echoes its request headers back. You are checking three things: that `SM_USER` is present exactly once and holds the OpenIAM principal rather than a browser-supplied value; that `SM_GROUPS` holds the groups you expect for that user; and that none of the three appear in the response headers in the browser's network panel, which is what confirms **Propagate Through Proxy** is clear.
{% endstep %}
{% endstepper %}

**The pattern is the filter.** These headers are attached to requests matching `/app/*` and to nothing else. There is no site-wide injection: to inject on two paths, put the metadata on both patterns, and to inject on only one HTTP method, put it on that method under **Supported HTTP Methods** rather than on the pattern. That is also the containment argument for injection — a header carrying identity is a credential to the application that trusts it, and the pattern is what stops it being sent to an upstream that has no business seeing it.

## Form fill

Form fill — the technique other vendors sell as **form-based single sign-on**, **credential injection** or **password vaulting** — is for the application that will not stop asking for a password. It has its own login form, you cannot turn it off, and it knows nothing about headers, SAML or OpenID Connect. The gateway answers by submitting that form on the user's behalf: the next time the browser asks for the form's action URL, the gateway turns the **GET** into a **POST**, fills in the fields you configured, and sends it upstream. The user never sees the application's login page, and never learns the password.

This is the answer to "we have a twenty-year-old application that only has a login page and we still want single sign-on to it".

Worked through here: an application published at `legacy.example.com/hr`, whose login form posts to `/hr/j_security_check`.

{% stepper %}
{% step %}

### Read the form's action and field names out of the page

Open the application's login page in a browser and view its source. Two things in the `<form>` tag are what you need:

{% code overflow="wrap" %}

```html
<form method="post" action="/hr/j_security_check">
  <input type="text"     name="j_username" />
  <input type="password" name="j_password" />
  <input type="hidden"   name="domain" value="corp" />
  <input type="checkbox" name="rememberMe" />
</form>
```

{% endcode %}

The `action` attribute — `/hr/j_security_check` — is the path your URI pattern must match. **Not** the path of the login page itself; the pattern goes on the action. The `name` of every `<input>` is a field you may have to send.

If the page source does not tell you, because the form is assembled by JavaScript at run time, capture a real sign-in instead with a network analyser or the browser's own network panel, sign in by hand, and read the POST target and its form parameters out of the capture. Copy the names character for character. A form fill that posts to the right URL with one field name wrong fails exactly like one that posts to the wrong URL — the application simply shows its login page again — so the two are indistinguishable from the browser.
{% endstep %}

{% step %}

### Create the URI pattern for the form action

Add a URI pattern whose **URI Pattern** is `/hr/j_security_check` on the content provider for `legacy.example.com`. An exact path, not a wildcard: this pattern exists to be the one request the gateway converts into a form post.
{% endstep %}

{% step %}

### Add the Form Post Processor metadata

On that pattern, scroll to **Metadata**, click **Add**, and give the entry a **Property Name** — `hr-form-fill`.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-1944bcb3d0b338da5c2395a2e0cc650a6615dae2%2Fgateway-inject-identity-05-formfill-name.png?alt=media" alt="The Metadata dialog with Property Name set to hr-form-fill and Metadata Type not yet chosen"><figcaption></figcaption></figure>

Set **Metadata Type** to **Form Post Processor**.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-3edb3fce73f7699aacc80c1a078fddb4aae0d9aa%2Fgateway-inject-identity-06-formfill-type.png?alt=media" alt="The same dialog with the Metadata Type list open, showing Form Post Processor among the options"><figcaption></figcaption></figure>

A **Content Type** field appears as soon as you choose that type, and only for that type. It is mandatory and the save is refused if it is blank, because the gateway has to encode a request body for a request that arrived without one. Type `application/x-www-form-urlencoded` for a standard HTML form, or `multipart/form-data` where the application's own form declares that encoding. The published OpenIAM documentation states that the short forms `urlencoded` and `form-data` are accepted in the same field; the full media types are unambiguous and worth preferring.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-30deedb5236d85c3f4bed4b17a28b3afa6a8cfbc%2Fgateway-inject-identity-07-formfill-content-type.png?alt=media" alt="The dialog with Metadata Type set to Form Post Processor and Content Type set to application/x-www-form-urlencoded, with the Metadata Attributes list still empty beneath"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add one metadata attribute per form field

Each attribute's **Property Name** is a form field name, copied from the page source:

| Property Name | Property Type           | Property Value |
| ------------- | ----------------------- | -------------- |
| `j_username`  | Value from User Profile | **Principal**  |
| `j_password`  | Value from User Profile | **Password**   |
| `domain`      | Static Value            | `corp`         |
| `rememberMe`  | Static Value            | `false`        |

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-8165878cc4e4777e9af520258e3b507356060394%2Fgateway-inject-identity-08-formfill-attribute.png?alt=media" alt="The Edit Attribute dialog with Property Name username, Property Type Value from User and Property Value Principal"><figcaption></figcaption></figure>

Send every field the real form sends, including hidden ones. A field you omit is a field the application does not receive.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-1f873edb3cec4147f17bdc4f0546627565294438%2Fgateway-inject-identity-09-formfill-complete.png?alt=media" alt="The saved Form Post Processor metadata with two attribute rows, username and password, both Value from User"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Prove the password is actually available

**Password** resolves the stored password for the user's identity on the managed system attached to the **content provider's** authentication provider — not the pattern's, and not the OpenIAM system unless that is what the provider points at. If the user has no identity on that managed system, or OpenIAM does not hold a recoverable password for it, the value comes back null and the form is posted **without a password**. Nothing errors and nothing is logged.

The symptom is unmistakable once you know it: the application's own login page comes back with the username already filled in and the password box empty. Check it before you go looking for anything else — open the user in the Webconsole, look at their **Identities**, and confirm one exists on that managed system. Where the password genuinely cannot be stored recoverably, supply the field from a **Groovy Script** value instead.
{% endstep %}

{% step %}

### Wait out the cache and sign in as a real user

Wait for the change to propagate, then request a protected page under `/hr/` as a user who has never signed in to the application. You should land on the application's authenticated home page without ever seeing its login form. Landing on the login form means one of: the pattern does not match the action URL, a field name is wrong, or the password was empty — in that order of likelihood.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**A CAPTCHA on the login form makes form fill impossible.** So does any other protection whose purpose is to establish that a real browser is submitting the form — a token generated by client-side JavaScript, a challenge signed in the page, a bot-detection script. The gateway posts the form from the server side, which is exactly what those measures exist to prevent, and no combination of metadata gets around it. Either the protection is disabled for requests originating at the proxy, or this application needs a different integration.
{% endhint %}

{% hint style="info" %}
**.NET applications usually need their hidden fields as well.** ASP.NET pages carry hidden inputs such as `__VIEWSTATE`, `__VIEWSTATEGENERATOR`, `__EVENTVALIDATION`, `__EVENTTARGET` and `__EVENTARGUMENT`, and the server validates them on POST, so a form fill that omits them is rejected even when the username and password are correct. Their values are generated per page render, so a **Static Value** will not do — you have to fetch them from the login page as it is served and submit them with the form, which means a **Groovy Script** value for each. It can be done, and the old OpenIAM documentation says so plainly while also calling it tricky. Treat a login form that needs this as a strong argument for asking whether the application can do SAML 2.0 or OAuth 2.0 after all.
{% endhint %}

## Verification

| What you changed                           | How to prove it took                                                                            | What a failure looks like                                                                              |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Header, parameter or cookie injection      | Read the headers the application received, or point the pattern at an endpoint that echoes them | Absent means the cache has not swept, the pattern did not match, or the value resolved to null         |
| Which identity the upstream is given       | **Principal Debug Tool**, `/webconsole-am/proxyURIPrincipalDebug.html`                          | The wrong principal, or none, for the authenticated user                                               |
| Which pattern the URL actually resolves to | **Federate URI Debug Tool**, `/webconsole-am/federateURIDebug.html`                             | A broader pattern than you expected — the metadata is on a pattern that never matched                  |
| Form fill                                  | Sign in as a user who has no session on the application                                         | The application's login page with the username filled and the password blank — no recoverable password |
| That the exclude rule works                | Send a request with a forged `SM_USER` attached                                                 | Exactly one `SM_USER` reaches the application, holding the OpenIAM principal                           |

## When it goes wrong

| Symptom                                                               | Cause                                                                                                                              | Fix                                                                                                                                                                                 |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An injected header never arrives                                      | The cache has not swept, the pattern does not match the URL, the metadata is on a different pattern, or the value resolved to null | Confirm the matched pattern with the **Federate URI Debug Tool**; a Groovy value returning `null` produces no header at all, which is the difference between it and **Empty Value** |
| Injected header arrives twice, or with a browser-supplied value       | No **Exclude Headers** entry for that header name                                                                                  | Add one naming the same header on the same pattern                                                                                                                                  |
| The injected header is also visible in the browser's response headers | **Propagate Through Proxy** is ticked on that value                                                                                | Clear it; identity claims belong in the upstream request only                                                                                                                       |
| Identity reaches an upstream that should not see it                   | The metadata is on a pattern broader than the application                                                                          | Move it to a narrower pattern, or to one HTTP method under **Supported HTTP Methods**                                                                                               |
| Cannot save a **Set Cookie** entry                                    | **Cookie Path** is required for that type and was left blank                                                                       | Set it; the check runs on save, not at request time                                                                                                                                 |
| Cannot save a **Form Post Processor** entry                           | **Content Type** is required for that type and was left blank                                                                      | Set `application/x-www-form-urlencoded`, or `multipart/form-data` where the form declares it                                                                                        |
| Form fill posts an empty password                                     | No recoverable password for that principal on the authentication provider's managed system                                         | Check the user's **Identities**; otherwise supply the value from a **Groovy Script** value                                                                                          |
| Form fill lands on the application's login page                       | The pattern does not match the form action, or a field name is wrong                                                               | Re-read the `action` and the `name` of every input from a captured sign-in                                                                                                          |
| Pages are slow only under the gateway                                 | A Groovy value doing I/O on every matching request                                                                                 | Reduce it to one lookup; it runs inside the authorization call the proxy waits on                                                                                                   |

## Roll it back

Delete the metadata entry on the pattern and wait out the cache. The application stops receiving the header on the next sweep, and reverts to whatever it did before — which, for an application whose own authentication you turned off, is unauthenticated access. Remove the injection and the network isolation together, or not at all.

## Related

* [Protect an application behind the Access Gateway](/administration-guide/gateway/protect-an-application.md) — the prerequisite
* [Advanced request and response transformations](/administration-guide/gateway/request-and-response-transformations.md) — URL rewriting, redirects and branding
* [Access Gateway reference](/administration-guide/gateway/access-gateway-reference.md#metadata-value-sources) — the six metadata types, the five value sources and the six user-profile attributes in full
* [Content providers and URI patterns](/administration-guide/access/content-providers-and-uri-patterns.md) — every field on the URI pattern screen

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/gateway/inject-identity.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
