> 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/consent/consent-and-it-policy.md).

# Consent and IT policy

Create a versioned, localized consent policy or privacy notice, gate a content provider with it, read the acceptance history as evidence, and let the user withdraw consent.

## What this is, and when you need it

OpenIAM calls this object an **IT policy**: an acknowledgement — acceptable use, privacy notice, terms of service — that a user must accept before the Access Gateway will let them through a particular content provider. It is unrelated to password and authentication policies, which are `POLICY` rows; an IT policy has its own table, screen and history.

Its three moving parts are a policy, one or more versions, and a per-language text block on each version. A content provider points at the policy; the runtime resolves the *active* version and the text for the *current* language; acceptance is recorded per user, per version, per content provider. The tables, screens and audit actions behind those parts are in [Consent data model and audit actions](/administration-guide/consent/consent-and-it-policy/consent-reference.md).

Note that "consent" is doing double duty in this product: OAuth 2.0 **scope** consent is a separate feature, and it interacts with this one through [a single property](#consent-and-the-oauth-authorize-url).

### Why an organisation needs one

Consent is the record that a **named person was shown a specific version of a specific text and agreed to it, on a date you can prove**. That last clause is the whole point, and it is what makes consent unlike every other setting in OpenIAM: the durable output is not a flag on a user, it is a row per user, per version, per content provider, and it survives the policy being superseded.

The recurring reasons to turn it on are an acceptable-use policy before an employee reaches an application, a privacy notice before a customer completes registration, and a periodic re-acknowledgement — the last of which is the **Renewal period** field rather than a scheduled job. What the resulting record does and does not prove is [after the procedure](#what-the-acceptance-record-proves).

## At a glance

|                          |                                                                                                                                                                                           |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | A versioned, localized policy that a user must accept before the Access Gateway lets them through one content provider, and an acceptance history row as evidence                         |
| **Where**                | **Administration → Consent configurations** (`/webconsole/it-policy`); the acknowledgement page at `/<context>/it-policy-use`; history at `/webconsole/it-policy-history`                 |
| **You need first**       | The approved text in **every** activated language, the numeric `LANGUAGE.ID` for each, a non-production content provider, and a working decline destination                               |
| **Takes**                | 30 to 45 minutes, most of it loading and reading back the per-language text                                                                                                               |
| **Reversible**           | Partly. Unlinking the content provider and deactivating the policy is clean. **Deleting a version cascades away the acceptance history**, which is irreversible and is your only evidence |

## Before you begin

* **The approved text, per language**, from whoever owns it legally. There is **no language fallback**, so you need every language you have activated — a missing one produces a blank agreement page, not an English one.
* **The numeric `LANGUAGE.ID`** for each, from [Languages and localization reference](/administration-guide/reference/languages.md) — the match is on the number, not the locale string.
* **A non-production content provider you can gate**, and a disposable identity that goes through it. One nullable column, `CONTENT_PROVIDER.IT_POLICY_ID`, is the whole enforcement wiring.
* **A working decline destination.** Declining redirects through `openiam.ui.logout.url`, which ships as `${org.openiam.idp.context.path}/logout` — `/idp/logout` with the default context path. Open it in a browser first: a broken decline path is invisible until the first user declines.

## Publish a consent policy and gate access on it

{% stepper %}
{% step %}

### Create the policy inactive

**Administration → Consent configurations → New** (`/webconsole/it-policy-edit`). Set **Name** — `IT_POLICY.NAME` is 64 characters — and **Description** (512). Choose the renewal period: `once`, `semiannually` or `annually`, and what each one computes is in [Renewal](#renewal-the-three-values-and-what-they-compute). Leave **Active** clear and leave **Effective date** empty for now: an inactive policy is a checkpoint you can inspect, and a future effective date makes an active policy silently non-enforcing, which is harder to reason about.

A duplicate name is rejected with `IT_POLICY_EXISTS`.
{% endstep %}

{% step %}

### Add version 1 and its text, one language at a time

Add a version, set its **Consent** and **Consent confirmation** text for every language you have activated. Both go into `IT_POLICY_VERSION_LANG_MAP` as `CONSENT` and `CONSENT_CONFIRMATION`; both are `LONGTEXT`, so length is not a constraint. Mark the version **Active**.

Only one version should be active. With two, OpenIAM takes whichever active version it encounters first, which is not a stable choice and is not a merge: users get one text or the other, and the same user can get different text on different requests.
{% endstep %}

{% step %}

### Save, reopen, and read the stored text back

Save. A new policy is audited as `IT_POLICY_ADD` and an edit as `IT_POLICY_MODIFY`. Reopen and confirm each language block persisted — particularly non-Latin scripts, which is where an encoding problem shows up first.
{% endstep %}

{% step %}

### Point one content provider at it

**Access Control → Content Providers**, open the *test* content provider, set **IT Policy**, save. That writes `CONTENT_PROVIDER.IT_POLICY_ID`. A policy with no content provider referencing it gates nothing, and the policy screen gives no indication of that.
{% endstep %}

{% step %}

### Activate the policy

Only now set **Active** on the policy. Enforcement begins at the next protected request through that content provider.
{% endstep %}

{% step %}

### Test accept, then decline, then a language with no text

Sign in as `<TEST_USER>` through the test content provider. You should land on the acknowledgement page at `/<context>/it-policy-use`, showing the active version's text in the user's current language.

Accept, and confirm a `USER_IT_POLICY_HISTORY` row with `ACCEPTED='Y'`, the right `VERSION_ID` and the right `CONTENT_PROVIDER_ID`. Sign in again and confirm you are not prompted.

With a second user, decline. Confirm the audit action is `DECLINE_IT_POLICY` and the browser is redirected to `openiam.ui.logout.url` with the postback URL appended — declining logs the user out rather than admitting them, on both the current and the legacy page.

Then set `?lang=` to an activated language for which you deliberately left the text empty, and confirm what your users would see. Do not skip this step; it is the one failure the product does not warn you about.
{% endstep %}

{% step %}

### Test revocation, both ways

This is **consent withdrawal**, and a privacy officer will ask for it by that name. Test both directions before you gate anything, because the user-side and administrator-side paths are different screens with different audit actions and different failure modes.

As the user, **SelfService → Self Service Center → Consent history**, revoke the acceptance. The confirmation reads *Are you sure you want to revoke your consent?* and warns *You will be logged out immediately and will need to accept the consent again the next time you log in.* The reset is refused with `IT_POLICY_RESET_BY_USER_FORBIDDEN` and HTTP 403 if the history row belongs to somebody else, and is audited as `IT_POLICY_RESET_BY_USER` when it succeeds. On success OpenIAM re-checks the current content provider's policy, and if it is no longer satisfied the user is logged out and prompted again on the next request — so revoking your own acceptance while signed in ends your session.

As an administrator, open the user and use **Consent history** at `/webconsole/it-policy-history` to reset a row — the confirmation says *This action will revoke the user's current consent and prompt them to accept the consent again upon their next login.* That is audited as `USER_IT_POLICY_HISTORY_RESET` with the requestor and target user both recorded, and the info line *"Consent agreement: `<NAME>` with ID = `<id>` successfully revoked"*. A database `DELETE` is not a substitute — it removes the evidence and leaves no audit trail of the removal.
{% endstep %}
{% endstepper %}

## Verification

| Evidence                             | Meaning                                                                                                                                                                   |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IT_POLICY_ADD` / `IT_POLICY_MODIFY` | The policy was created or changed. The event names the policy it applied to                                                                                               |
| `CONFIRM_IT_POLICY`                  | A user accepted. Target user and login are set; description reads *"User `<login>` with id = `<id>` accepted IT policy"*                                                  |
| `DECLINE_IT_POLICY`                  | A user declined and was redirected to logout                                                                                                                              |
| `IT_POLICY_SUBMIT`                   | The user submitted the acknowledgement page. The action is normally overwritten by one of the two above, so seeing it alone means the submission failed before the branch |
| `IT_POLICY_RESET_BY_USER`            | Self-revocation. A failure with `IT_POLICY_RESET_BY_USER_FORBIDDEN` means somebody tried to revoke another user's row                                                     |
| `USER_IT_POLICY_HISTORY_RESET`       | Administrator revocation, with both actors recorded                                                                                                                       |
| `IT_POLICY_DELETE`                   | The policy itself was deleted. A failure records `IT_POLICY_NOT_FOUND` and *"Consent with ID `<id>` was not found"*                                                       |
| `USER_IT_POLICY_HISTORY` row         | The acceptance itself: user, version, content provider, timestamp, accepted flag                                                                                          |

All six action constants live under the `IT_POLICY` audit target; retention properties exist for each and all ship `-1`. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md).

## When it goes wrong

| Symptom                                                    | Inspect                                                                                                             | Fix                                                                                                                                                                                                      |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No prompt at all                                           | `CONTENT_PROVIDER.IT_POLICY_ID` on the provider the request actually goes through                                   | The policy screen does not show which providers reference it. An unreferenced policy gates nothing                                                                                                       |
| No prompt, and the provider is correctly linked            | `IT_POLICY.ACTIVE`, then `IT_POLICY.EFFECTIVE_DATE`, then whether any version has `ACTIVE='Y'`                      | All three are required. A future effective date makes an active policy non-enforcing with no indication                                                                                                  |
| No prompt only on the OAuth path                           | `org.openiam.include.oauth.authorize.url.in.consent.scopes`                                                         | Ships `false`, which exempts the OAuth authorize URL                                                                                                                                                     |
| Prompt appears with no text on it                          | `IT_POLICY_VERSION_LANG_MAP` for the active `VERSION_ID` and the user's `LANGUAGE_ID`                               | There is no fallback. Add the row for that language                                                                                                                                                      |
| Users re-prompted on every request                         | Whether they are landing on `/usePolicy` rather than `/it-policy-use`, and whether `APPROVE_TYPE` is `SEMIANNUALLY` | The deprecated page has no `SEMIANNUALLY` branch and always re-prompts                                                                                                                                   |
| Users re-prompted after a "small" edit                     | `IT_POLICY_VERSION.VERSION_ID` against the `VERSION_ID` in their history rows                                       | A new version was created. History is keyed on version                                                                                                                                                   |
| Same user prompted again in a second application           | `USER_IT_POLICY_HISTORY.CONTENT_PROVIDER_ID`                                                                        | Acceptance is per content provider, by design                                                                                                                                                            |
| Two different texts appear at random                       | Count the versions with `ACTIVE='Y'`                                                                                | OpenIAM takes whichever it finds first. Exactly one version must be active                                                                                                                               |
| Decline lets the user into the application                 | `openiam.ui.logout.url`                                                                                             | Decline redirects to that URL. A wrong or empty value means the redirect does nothing                                                                                                                    |
| Revocation appears to have no effect                       | The current content provider's `IT_POLICY_ID` against the revoked row's                                             | OpenIAM logs the user out on revocation only when the *current* provider's policy is no longer satisfied. Otherwise enforcement happens at the next protected request                                    |
| Administrator cannot revoke, and the revocation is refused | Which screen the revocation was attempted from                                                                      | **SelfService → Self Service Center → Consent history** lets a user revoke only their own row. An administrator revokes from **Consent history** on the user's record at `/webconsole/it-policy-history` |
| Save rejected as a duplicate                               | `IT_POLICY.NAME`                                                                                                    | `IT_POLICY_EXISTS`. Names are unique                                                                                                                                                                     |

## Roll it back

Clear **IT Policy** on the content provider first and confirm the unprotected path works, then deactivate the policy. Do **not** delete the policy or its versions to undo a launch: deleting a version cascades away the acceptance history that proves who agreed to what, and that history is your only record. Keep the failed version, mark it inactive, and activate a corrected one.

## What the acceptance record proves

**The vocabulary, for anyone comparing products.** This one object is what you use for a privacy notice, a terms-of-service acceptance and an acceptable-use policy; the history row is the consent record; and the reset above is consent withdrawal. What OpenIAM does not have is a purpose register — an IT policy is one text with one acceptance, not a set of separately consentable purposes — or any data subject request workflow. See [If you arrived here from a privacy or GDPR requirement](/administration-guide/consent.md#if-you-arrived-here-from-a-privacy-or-gdpr-requirement).

Three properties of the model do the work, and each maps onto an obligation somebody will eventually ask you to demonstrate:

| Property                                  | What it gives you                                                                                                                       | Where it comes from                                                                                      |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| It is **evidence**                        | A dated, attributable acceptance you can produce for an auditor, a customer contract or an internal control, per application            | The `USER_IT_POLICY_HISTORY` row, which cannot be re-pointed at a different user or version once written |
| It is **versioned**                       | A material change to the terms re-asks everybody, automatically, because no existing acceptance matches a new version                   | Acceptance is keyed on `VERSION_ID`                                                                      |
| It is **enforced at the point of access** | The same person can be asked once for one application and separately for another, so "the user consented" is never a vague global claim | Acceptance is keyed on `CONTENT_PROVIDER_ID` too                                                         |

**What consent is not.** It is not authentication and it is not authorisation. It does not decide who someone is or what they may do; it decides whether they may proceed *until they have agreed*, and it produces the record that says they did. If you need to block access on an attribute rather than on an agreement, that is an authentication rule or an access policy instead.

**What weakens the evidence.** Two things, both avoidable. Editing an accepted version's text in place means the history screen renders the *current* wording rather than a snapshot of what was shown, so an existing acceptance now points at different words. And a user who acknowledged through the deprecated `/usePolicy` page has a timestamp and **no history row at all** — which reads as "never accepted" everywhere. See [Consent data model and audit actions](/administration-guide/consent/consent-and-it-policy/consent-reference.md).

## Renewal: the three values and what they compute

**Renewal period** accepts three values, each shown in lower case on screen:

| Value          | Display string | Re-prompt when                                             |
| -------------- | -------------- | ---------------------------------------------------------- |
| `ONCE`         | `once`         | Never, once accepted for this version and content provider |
| `SEMIANNUALLY` | `semiannually` | `now >= submitDate + 6 months`                             |
| `ANNUALLY`     | `annually`     | `now >= submitDate + 1 year`                               |

The evaluation is stricter than the field names suggest. A user is treated as *already accepted* only when **all** of these hold:

1. The policy is `ACTIVE`.
2. `EFFECTIVE_DATE` is null, or now is after it. Before the effective date the policy is not enforced at all — an inactive gate, not a pending one.
3. At least one version is `ACTIVE`.
4. A `USER_IT_POLICY_HISTORY` row exists with `ACCEPTED='Y'`, that **exact `VERSION_ID`**, and the **current `CONTENT_PROVIDER_ID`**. All three must match; any one of them differing means not accepted.
5. The newest such row by `SUBMIT_DATE` still falls inside the renewal window above.

Two consequences follow directly and both are frequently misread.

**Acceptance is per content provider.** The same user accepting the same version behind a second content provider is prompted again, and gets a second history row. That is by design — the history row records *where* the acknowledgement was given — but it means "the user already accepted" is never a global statement.

**Activating a new version re-prompts everybody.** History is keyed on `VERSION_ID`, so the moment you set `ACTIVE='Y'` on a new version, no acceptance matches and every user is prompted at the next protected request. That is the correct mechanism for a material change; it is also why a typo fix should be an in-place edit of the current version rather than a new one.

{% hint style="warning" %}
The legacy `/usePolicy` page understands only `ANNUALLY` and `ONCE`. A `SEMIANNUALLY` policy reached through it is treated as never accepted, so the user is re-prompted on **every** request — an endless acknowledgement loop rather than an error. Another reason to stay on `/it-policy-use`.
{% endhint %}

## Localization has no fallback

{% hint style="danger" %}
**A missing `IT_POLICY_VERSION_LANG_MAP` row for the user's language produces a blank agreement page, not an English one.** OpenIAM resolves the active version, looks for a language map matching the current language id, and when nothing matches returns empty text: no substitution from a default language, and no error anywhere. The user then sees an acknowledgement screen with no terms on it and, in most designs, an Accept button. Treat complete language coverage as a publication gate, and test the unsupported-language case explicitly before activating — step 6 of the procedure does exactly that.
{% endhint %}

Which language is "current" is the `OPENIAM_LOCALE` cookie resolved to a `LANGUAGE` row — see [Languages and localization](/administration-guide/platform/localization.md#which-locale-is-actually-in-force). The match is made on the **language id**, so it is the numeric `LANGUAGE.ID` that has to equal your `IT_POLICY_VERSION_LANG_MAP.LANGUAGE_ID`, not the locale string. Take the ID from [Languages and localization reference](/administration-guide/reference/languages.md) rather than assuming it follows the code.

## Consent and the OAuth authorize URL

One property decides whether an OAuth authorization request is subject to the IT policy at all.

| Property                                                    | Ships as    | Category         | Screen label                                      |
| ----------------------------------------------------------- | ----------- | ---------------- | ------------------------------------------------- |
| `org.openiam.include.oauth.authorize.url.in.consent.scopes` | **`false`** | `Authentication` | Include OAuth2 Authorization URL in Consent Scope |

The rule is short. For any request that is **not** to the OAuth authorize URL, the IT policy always applies. For a request **to** the OAuth authorize URL it applies only when this property is `true` and the session has not already been sent to the policy page once. That second condition exists to break a redirect loop and is held on the session, so it resets when the session does — which is why a retest after changing the property has to start from a fresh session, not a reload.

{% hint style="warning" %}
With the shipped `false`, a user who reaches an application purely through an OAuth authorization request is **not** shown the consent page, even though the content provider names an IT policy. If your obligation is "no access without acknowledgement", set this to `true` and re-test the OAuth flow for redirect loops — the guard is a session attribute, so test with a fresh session.
{% endhint %}

## Update in place, or new version?

| Change                                                                          | Do this                                                                                                                                                                            |
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Typo, formatting, a broken link — meaning unchanged                             | Edit the active version's text. Nobody is re-prompted, and existing acceptances still point at text that means the same thing                                                      |
| New obligation, changed data use, changed audience, changed decline consequence | New version, activate it. Everyone is re-prompted, because history is keyed on `VERSION_ID`                                                                                        |
| Adding a language that was missing                                              | Add the row to the **active** version. Users of other languages are unaffected                                                                                                     |
| Materially revising an existing translation                                     | New version. Editing in place means an existing acceptance now points at different words                                                                                           |
| Changing the renewal period                                                     | Edit the policy. It takes effect at the next evaluation, comparing against existing `SUBMIT_DATE` values — shortening `ANNUALLY` to `SEMIANNUALLY` can re-prompt users immediately |

When in doubt, new version — see [What weakens the evidence](#what-the-acceptance-record-proves).

## Related

* [Languages and localization](/administration-guide/platform/localization.md) — how the current language is resolved, and why the missing-row behaviour differs here
* [Content providers and URI patterns](/administration-guide/access/content-providers-and-uri-patterns.md) — the object that carries `IT_POLICY_ID`
* [Enumerated value reference](/administration-guide/reference/enums.md#consent-renewal-frequency) — the three `ITPolicyApproveType` values
* [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) — the six `IT_POLICY` actions and their retention keys

*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/consent/consent-and-it-policy.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.
