> 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/security/authentication-model.md).

# The authentication model

How a request is matched to a content provider, a rule and a sequence of authentication steps, which object each decision actually lives on, and how to build a passwordless sign-in.

## What this is, and when you need it

Every "the second factor is configured but never prompts" ticket, and every "why is this application asking for a password when it should be using Kerberos" ticket, is a question about *which object made the decision*. This page names the objects, the tables behind them, and the exact order in which OpenIAM consults them.

This page is the model, and every configuration task hands back to it when a change does not take effect. Deciding which provider type and which second factor you need is [Authentication providers and MFA](/administration-guide/security/authentication-providers-and-mfa.md); demanding a stronger factor on one path is [Step-up authentication](/administration-guide/security/step-up-authentication.md); the exhaustive level, rule and capability-flag tables are in [Authentication levels, rules and capability flags](/administration-guide/security/authentication-providers-and-mfa/authentication-provider-reference.md). Read this page to know which of the five objects made the decision you are looking at, then go there to change it. The walk-through at the end is a proof exercise, not a configuration procedure.

## Five objects, five different questions

| Object                        | Table                 | The question it answers                                            |
| ----------------------------- | --------------------- | ------------------------------------------------------------------ |
| Content provider              | `CONTENT_PROVIDER`    | Which hosted context is serving this URL?                          |
| URI pattern                   | `URI_PATTERN`         | Which specific path within that context matched, and is it public? |
| Authentication rule           | `AUTH_RULE`           | Which sequence of proofs is demanded?                              |
| Authentication level grouping | `AUTH_LEVEL_GROUPING` | Which selectable instance of a kind of proof is this step?         |
| Authentication provider       | `AUTH_PROVIDER`       | How is the credential actually verified, and under which policies? |

An **authentication level** (`AUTH_LEVEL`) is the *kind* of proof — password, certificate, TOTP, a FIDO assertion. A **grouping** is a named, selectable instance of one level, and as of 4.2.1.11 a grouping can also pin one `AUTH_PROVIDER_ID`, so two groupings of `SMS_AUTH` can route to two different OTP tenants. A rule chains groupings. A content provider or URI pattern attaches the rule. The provider decides how the credential is checked and which password and authentication policies apply.

The full level catalogue, with the IdP page each one renders and its `REQUIRES_AUTHENTICATION` value, is in [Authentication levels, rules and capability flags](/administration-guide/security/authentication-providers-and-mfa/authentication-provider-reference.md#the-authentication-step-types).

## A rule is a directed graph, not a list

Two tables hold the graph, both added in 4.2.0.0:

| Table                           | Columns                                                                                                                           | Role     |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `RULE_AUTHENTICATION_STEP`      | `ID`, `RULE_ID`, `AUTH_LEVEL_GROUPING_ID`, `ADAPTIVE_RISK_ID`, plus `POS_TOP` and `POS_LEFT` for the editor canvas and `LIFETIME` | One node |
| `RULE_AUTHENTICATION_STEP_XREF` | `ID`, `PARENT_ID`, `CHILD_ID`, `PARENT_RISK_RESULT`                                                                               | One edge |

A step carries **either** an `AUTH_LEVEL_GROUPING_ID` (do this factor) **or** an `ADAPTIVE_RISK_ID` (evaluate this signal). OpenIAM refuses to save a step that sets both. If the graph editor will not accept a step, check that you have set one or the other and not left a risk on a factor step.

### `PARENT_RISK_RESULT` is the branch

`PARENT_RISK_RESULT` is a `CHAR(1)` holding `Y` or `N`, and an edge whose value is neither is read as `Y`. Every one of the 50 edges seeded by the shipped rules is `'Y'` — a branch exists only where someone has added the `N` edge. The Verification section below gives the query that shows which edges a risk step actually has.

OpenIAM walks the graph like this:

* Root steps are those with no parent edge.
* An **adaptive-risk** step evaluates to true or false, and the walk continues down the edges whose `PARENT_RISK_RESULT` matches that result. That is how a rule expresses a branch: two outgoing edges from the same risk step, one `Y` and one `N`.
* On an **authentication-level** step, if the user has already satisfied that level earlier in the same request, the walk follows the `Y` edges and skips the factor; otherwise the step is added to the list of factors the user must complete.

The simplest shipped rule is two steps and one edge — password, then accept. These are the rows behind it, shown so you can recognise the shape when you query the tables. Build and edit rules on **Policy → Authentication Rule**, not with `INSERT`:

{% code overflow="wrap" lineNumbers="true" %}

```sql
INSERT INTO RULE_AUTHENTICATION_STEP (ID, RULE_ID, AUTH_LEVEL_GROUPING_ID, ADAPTIVE_RISK_ID)
VALUES ('DEF_PASSWD_AUTH_RULE_1','DEF_PASSWD_AUTH_RULE','PASSWORD_AUTH',NULL);
INSERT INTO RULE_AUTHENTICATION_STEP (ID, RULE_ID, AUTH_LEVEL_GROUPING_ID, ADAPTIVE_RISK_ID)
VALUES ('DEF_PASSWD_AUTH_RULE_2','DEF_PASSWD_AUTH_RULE','AUTHORIZATION_ACCEPT',NULL);
INSERT INTO RULE_AUTHENTICATION_STEP_XREF (ID, PARENT_ID, CHILD_ID, PARENT_RISK_RESULT)
VALUES ('DEF_PASSWD_AUTH_RULE_XREF','DEF_PASSWD_AUTH_RULE_1','DEF_PASSWD_AUTH_RULE_2','Y');
```

{% endcode %}

### The two terminal steps

`AUTHORIZATION_ACCEPT` and `AUTHORIZATION_DECLINE` are `AUTH_LEVEL` rows like any other, but they terminate a path rather than rendering a page. Their IDs are configuration, not literals: `org.openiam.auth.level.type.accept.id` and `org.openiam.auth.level.type.decline.id`, which ship as `AUTHORIZATION_ACCEPT` and `AUTHORIZATION_DECLINE` and are set separately for the IdP and for the ESB. Both are file properties rather than console settings, so you cannot read the running values from the Webconsole — if a deployment has renamed either level, the deployment owner is the one who knows.

`AUTHORIZATION_DECLINE` is the one to understand. When a request arrives with no adaptive-authentication extra data at all, OpenIAM injects the decline grouping and records *"Mandatory extra data don't present"* in the ESB log; a grouping list containing decline then fails the request with `UNAUTHORIZED`. A path that reaches decline is refused; a path with no `AUTHORIZATION_ACCEPT` terminal never grants a session.

**Chained steps mean AND. Sibling root steps mean OR.** `DEF_ANY_AUTH_RULE` is the canonical any-of example: `CRIIPTO_AUTH`, `PASSWORD_AUTH`, `WEB_AUTH` and `CERT_AUTH` are four separate root steps that each edge to the single `DEF_ANY_AUTH_RULE_2` accept step, with `DUO_AUTH` added in 4.2.1.8 and `KERB_AUTH`, `PUSH_AUTH`, `SMS_AUTH`, `TOTP_AUTH`, `EMAIL_AUTH`, `CHALLENGE_RESPONSE_AUTH` and `VOICE_AUTH` bolted onto the same rule in 4.2.2.0. Open the rule on **Policy → Authentication Rule** to see how many roots yours has now.

## Passwordless sign-in

**A password is one step among many, and no rule is obliged to contain one.** OpenIAM's sign-in page asks for an identifier, not a credential: the user types their login on `/idp/login`, OpenIAM resolves the rule protecting the destination, and the browser is then sent to the page for the first factor that rule still demands. Where that factor is anything other than `PASSWORD_AUTH`, no password is asked for at any point. **Passwordless authentication in OpenIAM is a rule you build, not a feature you switch on**, and several of the rules that do it already ship.

Each of these is a single root step edging straight to `AUTHORIZATION_ACCEPT` — one proof, then in:

| Shipped rule                                                                        | Root step                 | What the user actually does                                                            |
| ----------------------------------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------- |
| `DEF_WEB_AUTH_RULE` — **Def. Web authentication**                                   | `WEB_AUTH`                | Touches a security key, or approves with Touch ID or Windows Hello, on `/idp/auth-web` |
| `DEF_CERT_AUTH_RULE` — **Def. Certificate Auth Rule**                               | `CERT_AUTH`               | Presents a client certificate or smart card on `/idp/auth-cert`                        |
| `DEF_KERB_AUTH_RULE` — **Def. Kerberos Auth Rule**                                  | `KERB_AUTH`               | Nothing visible — the workstation's existing Kerberos ticket is accepted               |
| `DEF_CRIIPTO_AUTH_RULE` — **Def. criipto authentication**                           | `CRIIPTO_AUTH`            | Completes a national eID flow and returns to `/idp/auth-criipto`                       |
| `DEF_DUO_AUTH_RULE` — **Def. DUO authentication**                                   | `DUO_AUTH`                | Approves the prompt in Duo                                                             |
| `DEF_CHALLENGE_RESPONSE_AUTH` — **Def. Challenge Response Question authentication** | `CHALLENGE_RESPONSE_AUTH` | Answers their security questions on `/idp/auth-questions`                              |

`DEF_ANY_AUTH_RULE` — **Any authentication** — is the same idea offered as a choice: twelve roots, each edging to the one accept step, so any single one of them is sufficient. When a rule leaves more than one factor outstanding the user is sent to `/idp/auth-select` and reads **Choose how you would like to verify your identity**, which is where a population half-way through a FIDO rollout can pick a security key while everyone else picks a password.

Two limits matter before you promise this to anybody.

**It is passwordless, not usernameless.** The login is still collected on `/idp/login` — submit it empty and you get *Login is a required field* — and OpenIAM uses it to work out which credentials to allow. There is no discoverable-credential flow, so "sign in with a passkey" with no identifier typed is not something this product does. See [What OpenIAM's WebAuthn implementation does and does not do](/administration-guide/security/fido2-and-webauthn/fido2-and-webauthn-reference.md).

**One passwordless factor is one factor.** A `WEB_AUTH` step is **phishing-resistant** — the browser will not sign for a different origin, which is what makes it stronger than any code the user can be talked into repeating — but user verification is not enforced, so on its own it proves possession and not identity. If your control objective is two factors, chain the passwordless step after another one rather than making it a sibling root.

{% hint style="danger" %}
A rule whose only paths are passwordless locks out everyone who has not enrolled, and enrolment lives behind the sign-in they cannot complete. Attach it to one narrow URI pattern first, keep `DEF_PASSWD_AUTH_RULE` on a route you have tested, and read [Keep a working way in](/administration-guide/security/authentication-providers-and-mfa.md#keep-a-working-way-in) before you touch a content provider.
{% endhint %}

## How a request selects a rule

Rules attach through two cross-reference tables, both with an `EXEC_ORDER INT(3)`: `CP_RULE_XREF (CONTENT_PROVIDER_ID, RULE_ID, EXEC_ORDER)` and `URI_RULE_XREF (URI_PATTERN_ID, RULE_ID, EXEC_ORDER)`, both added in 4.2.0.0. Both have a unique constraint on the whole triple, so the same rule may appear twice on one content provider only at different `EXEC_ORDER` values.

{% hint style="warning" %}
**A URI pattern's rules replace the content provider's rules — they do not add to them.** The choice is either/or, and OpenIAM makes it the same way both when it works out which factors a request needs and when it builds the authentication request: if the matched pattern has any rule of its own, those rules are used, in `EXEC_ORDER`; only if it has none does OpenIAM fall back to the content provider's.

Attach one rule to a URI pattern and that pattern stops honouring the content provider's rules entirely, including `DEF_PASSWD_AUTH_RULE`. If you want password *and* a second factor on a path, the pattern's own rule must contain both steps. To confirm which side is in force, query `URI_RULE_XREF` for the matched pattern: any row at all there means the content provider's rules are not consulted for that path.
{% endhint %}

The 4.2.0.0 upgrade that introduced rules gave **every** content provider `DEF_PASSWD_AUTH_RULE` at `EXEC_ORDER` 0 if it had none. That row is why password sign-in works on a fresh install, and it is the row to leave alone.

Entitlement is a separate, later check on the same call. OpenIAM refuses with `UNAUTHORIZED` when a non-public URI pattern's resource is not granted to the user, then again for the content provider's resource, then again for the resource on the matched HTTP method. **Passing authentication does not grant the destination.**

## How policy and provider relate

Two policies hang off a provider, and they are not interchangeable:

| Column                         | Field                 | Shipped on `OPENIAM_DEFAULT_AUTH` |
| ------------------------------ | --------------------- | --------------------------------- |
| `AUTH_PROVIDER.POLICY_ID`      | Password policy       | `4000`                            |
| `AUTH_PROVIDER.AUTH_POLICY_ID` | Authentication policy | `4001`                            |

A provider has carried both since 4.0.0.0, and both appear as separate pickers when you open the provider on **Access Control → Authentication Providers** — that is where to check which policy a provider is actually using. Policy `4001` holds the lockout threshold (`FAILED_AUTH_COUNT`), auto-unlock time (`AUTO_UNLOCK_TIME`), token life and the login module name; policy `4000` holds composition, history and expiration. See [Password and authentication policy parameters](/administration-guide/reference/policy-parameters.md).

The provider's password policy is only **one candidate** in password-policy resolution — roles, groups, organizations and the managed system also contribute, and the highest `PRIORITY` wins. That resolution is documented in full in [Password policy](/administration-guide/security/password-policy.md#how-a-policy-is-selected-for-a-user).

Which provider serves a request comes from the content provider (`CONTENT_PROVIDER.PROVIDER_ID`); when nothing resolves, OpenIAM falls back to `org.openiam.default.auth.provider.id`, which ships as `OPENIAM_DEFAULT_AUTH` and records *"No Content Provider of URI Pattern information provided, or could not resolve. Using default AUth Provider"* in the ESB log. That line is the signal that your content provider is not matching at all, not that the provider is misconfigured.

## Work through one real request

{% stepper %}
{% step %}

### The URL resolves to a content provider

OpenIAM matches the request against each content provider's `DOMAIN_PATTERN` and context. No match means `URI_FEDERATION_CONTENT_PROVIDER_NOT_FOUND`, and the answer handed back to the proxy is marked not configured and not cacheable, so the proxy does not cache the miss and will ask again on the next request.
{% endstep %}

{% step %}

### The path resolves to a URI pattern and method

Within the matched content provider, OpenIAM resolves both the URI pattern and the specific HTTP method entry on it. The pattern's `MATCH_MODE` values are in [Enumerated value reference](/administration-guide/reference/enums.md).
{% endstep %}

{% step %}

### The pattern's rules win, else the content provider's

Ordered by `EXEC_ORDER`. See the warning above.
{% endstep %}

{% step %}

### The rule graph is walked to a list of required factors

Adaptive-risk steps are evaluated now and branch on `PARENT_RISK_RESULT`; already-approved levels are skipped. The result is the set of groupings the IdP will demand.
{% endstep %}

{% step %}

### The attempt is recorded to the statistics index

Unless the resulting grouping list contains the `NONE` auth level — whose ID comes from `org.openiam.auth.level.type.none.id` and ships as `NONE` — OpenIAM writes one document to the OpenSearch index `urifederationstatistic`. That index is the training corpus for behavioural risk, so a path whose rule resolves to `NONE` contributes nothing to it.
{% endstep %}

{% step %}

### Entitlement is checked, then the destination is served

Pattern resource, then content-provider resource, then method resource. A decline grouping anywhere in the list fails the request with `UNAUTHORIZED`.
{% endstep %}
{% endstepper %}

## Caches, and why your change has not taken effect

Nothing in this model is read from the database per request. Four sweeps matter:

| Property                                   | Shipped value | What it holds stale                  |
| ------------------------------------------ | ------------- | ------------------------------------ |
| `org.openiam.auth.provider.sweep.time`     | `60000` ms    | Providers and rules                  |
| `org.openiam.policy.sweep.time`            | `72000` ms    | Password and authentication policies |
| `org.openiam.content.provider.threadsweep` | `300000` ms   | Content providers and URI patterns   |
| `org.openiam.property.value.threadsweep`   | `300000` ms   | System property values               |

All four are read from the IdP's property file at startup rather than from the Webconsole, so you cannot confirm the running intervals from the console. If a deployment has changed them, the deployment owner is the one who knows; the shipped values above are what you get if nobody has.

The rule cache is the exception. OpenIAM refreshes it immediately the moment a request asks for a rule ID it has not cached, so a *newly attached* rule takes effect at once while an *edited* one waits out the sweep. That asymmetry is the single most common reason a rule change appears not to work.

## Verification

| What to check                                             | How                                                                                                                                                                                                                      |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Which rules a path resolves to                            | `/webconsole/authenticationDebug` reports how the request was evaluated                                                                                                                                                  |
| The rule graph is complete                                | Every root step must have a path to a step whose grouping is `AUTHORIZATION_ACCEPT`. `/webconsole/authentication-rule` renders the graph                                                                                 |
| The rule is attached where you think                      | `SELECT * FROM CP_RULE_XREF WHERE RULE_ID='<rule>'` and `SELECT * FROM URI_RULE_XREF WHERE RULE_ID='<rule>'`                                                                                                             |
| A pattern is not silently overriding the content provider | If `URI_RULE_XREF` has any row for the matched pattern, the content provider's rules are not consulted                                                                                                                   |
| The branch fired the way you expected                     | `SELECT PARENT_ID, CHILD_ID, PARENT_RISK_RESULT FROM RULE_AUTHENTICATION_STEP_XREF WHERE PARENT_ID='<risk step>'` — you should see both a `Y` and an `N` row for a real branch                                           |
| The attempt was recorded                                  | One new document in the `urifederationstatistic` index for the user                                                                                                                                                      |
| Audit                                                     | `LOGIN`, `DO_LOGIN`, `DO_LOGIN_AGAINST_MANAGED_SYSTEM`, and `SAVE_AUTH_RULE` / `DELETE_AUTH_RULE` for the edit itself. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) |

## When it goes wrong

| Symptom                                                       | Cause                                                                                                                                                                       | Fix                                                                                                                                                      |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Second factor configured, never prompts                       | The matched URI pattern has its own `URI_RULE_XREF` row, so the content provider's rule is not consulted                                                                    | Put every required step in the pattern's rule, or remove the pattern's rule row                                                                          |
| Everyone follows the same branch out of a risk step           | Only one edge exists, or both edges carry `PARENT_RISK_RESULT='Y'`                                                                                                          | Add the missing edge with the opposite value                                                                                                             |
| Users authenticate but land on `403`                          | Entitlement, not authentication — the pattern, content provider or method resource is not granted                                                                           | Grant the resource, or mark the pattern public                                                                                                           |
| `UNAUTHORIZED` with nothing in the log about credentials      | The grouping list contained `AUTHORIZATION_DECLINE`, often because required adaptive extra data was absent — look for *"Mandatory extra data don't present"* in the ESB log | Supply the extra data (IP, device, user agent) from the proxy, or remove the risk step                                                                   |
| A step carries both a grouping and a risk and then misbehaves | OpenIAM refuses that combination on save now, but a row written before that check existed survives an upgrade                                                               | One step, one purpose. Query `RULE_AUTHENTICATION_STEP` for rows where both `AUTH_LEVEL_GROUPING_ID` and `ADAPTIVE_RISK_ID` are non-null, and split them |
| Rule name saves but the canvas is empty                       | The graph editor needs the persisted rule ID                                                                                                                                | Save, reopen, then build the graph                                                                                                                       |
| Edited rule behaves the same                                  | Rule edits wait for the sweep; only a rule ID the cache has never seen forces an immediate refresh                                                                          | Wait out `org.openiam.auth.provider.sweep.time` (60 s)                                                                                                   |

## Related

* [Step-up authentication](/administration-guide/security/step-up-authentication.md) — approved levels, per-step lifetime, and what an unmet demand looks like
* [Terminate a user's sessions](/administration-guide/security/session-management.md) — what the granted session is, and what ends it
* [FIDO2 and WebAuthn](/administration-guide/security/fido2-and-webauthn.md) — the strongest shipped factor, its limits, and the passwordless rollout
* [Authentication providers and MFA](/administration-guide/security/authentication-providers-and-mfa.md) — the provider catalogue, the capability flags and every second factor
* [Adaptive authentication](/administration-guide/security/adaptive-authentication.md) — `ADAPTIVE_RISK` and the `ADAPTIVE_RISK_ID` branch step
* [Content providers and URI patterns](/administration-guide/access/content-providers-and-uri-patterns.md) — the routing layer above rules
* [Password and authentication policy parameters](/administration-guide/reference/policy-parameters.md)
* [Change configuration safely](/administration-guide/start/change-safely.md)

*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/security/authentication-model.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.
