> 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/duo.md).

# Add Duo as a second factor

Register a Duo tenant as its own authentication provider, reference it from an authentication policy provider, and attach the shipped Duo rule.

## What this is, and when you need it

A Duo tenant is its own authentication provider row of type `DUO_AUTH_PROVIDER`. It is never picked directly at sign-in: an `OPENIAM_POLICY` provider references it through the **DUO Authentication Provider** field, and an authentication rule with a `DUO_AUTH` step is what demands it. Choose Duo when you already run a Duo tenant and want to reuse its enrolments and policies; starting from nothing, [TOTP](/administration-guide/security/totp.md) and [FIDO2](/administration-guide/security/fido2-and-webauthn.md) need no third party.

## At a glance

|                          |                                                                                                                                                                                           |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | A test identity redirected to Duo after their password, returning to OpenIAM with a session, on one protected URL                                                                         |
| **Where**                | **Access Control → Authentication Providers** (`/webconsole/authenticationProviders`) — two provider rows — and **Policy → Authentication Rule** (`/webconsole/authentication-rule-list`) |
| **You need first**       | A Duo application's client ID, client secret and API hostname; an `OPENIAM_POLICY` provider you can edit; a tested break-glass administrator on a route the new rule does not cover       |
| **Takes**                | 30–45 minutes, most of it registering the redirect URI on the Duo side                                                                                                                    |
| **Reversible**           | Yes — detach the rule and clear **DUO Authentication Provider**; nothing is created on the OpenIAM side that survives                                                                     |

## Before you begin

Do [Keep a working way in](/administration-guide/security/authentication-providers-and-mfa.md#keep-a-working-way-in) first, in a second browser session, and leave it open.

## Configure Duo

{% stepper %}
{% step %}

### Register the redirect URI in the Duo admin panel

Duo returns the browser to `/auth-duo-callback` in the IdP application. The absolute value to register is therefore:

{% code overflow="wrap" %}

```
https://<OPENIAM_HOST>/idp/auth-duo-callback
```

{% endcode %}

It is not a guess and it is not configurable. Register that exact string in Duo before you create anything in OpenIAM.
{% endstep %}

{% step %}

### Create the `DUO_AUTH_PROVIDER` row

**Access Control → Authentication Providers → New** (`/webconsole/newAuthProvider`), type **DUO Auth Provider**. Four attributes, all `REQUIRED='Y'` and all single-valued, so the form will not save with any of them empty:

| `AUTH_ATTRIBUTE_ID` | `ATTRIBUTE_NAME` | Field label   | Value                                          |
| ------------------- | ---------------- | ------------- | ---------------------------------------------- |
| `DuoAuth1`          | `CLIENT_ID`      | Client ID     | The Duo application's client ID                |
| `DuoAuth2`          | `CLIENT_SECRET`  | Client secret | The Duo application's client secret            |
| `DuoAuth3`          | `API_HOSTNAME`   | API hostname  | `api-<id>.duosecurity.com`                     |
| `DuoAuth4`          | `REDIRECT_URI`   | Redirect URI  | `https://<OPENIAM_HOST>/idp/auth-duo-callback` |

OpenIAM passes all four values to Duo exactly as you type them — no trimming, no normalisation — so a trailing space or an `https://` prefix in **API hostname** reaches Duo and fails there rather than in the Webconsole.
{% endstep %}

{% step %}

### Reference it from the `OPENIAM_POLICY` provider

Open the authentication-policy provider the content provider points at, tick **Supports DUO Authentication**, and select the row you just created in **DUO Authentication Provider**.

That selector lists existing provider rows rather than taking free text, because `AUTH_PROVIDER.DUO_PROVIDER_ID` is a self-reference into `AUTH_PROVIDER(PROVIDER_ID)`. If the section is absent altogether, you are on the wrong provider type: `SUPPORTS_DUO_AUTH` ships `Y` on `OPENIAM_POLICY` and nothing else. Saving with the checkbox on and the selector empty is refused with *"DUO Authentication Provider not set"*.
{% endstep %}

{% step %}

### Attach a rule with a `DUO_AUTH` step to one narrow URI pattern

Duo ships its own plumbing: the `DUO_AUTH` level, the `DUO_AUTH` grouping, the rule `DEF_DUO_AUTH_RULE` (`DUO_AUTH` → `AUTHORIZATION_ACCEPT`), and a `DUO_AUTH` root added to `DEF_ANY_AUTH_RULE`. You build none of it; you attach it.

`DEF_DUO_AUTH_RULE` demands Duo *instead of* a password. For Duo as a **second** factor, build `PASSWORD_AUTH` → `DUO_AUTH` → `AUTHORIZATION_ACCEPT` — chained steps mean *and*. Attach it to one test URI pattern at `/webconsole/editProviderPattern`, never to `DEFAULT_CP` on a first test.
{% endstep %}

{% step %}

### Wait a sweep, then sign in as the test identity

`org.openiam.auth.provider.sweep.time` is `60000` ms. Expect the password page, then a redirect to Duo at `/idp/auth-duo`, then the return to your destination.

Duo's callback lands on the IdP application, and the `state` that correlates it is generated by Duo and cached in Redis against the login, provider ID and postback URL. If Redis is unavailable the sign-in fails at the callback with the generic message `DUO code expired or got wrong state` — the same message a mismatched redirect URI produces, so confirm Redis health before you conclude the URI is wrong.
{% endstep %}
{% endstepper %}

## Verification

| What to check                                | How                                                                                                                                                                              |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The reference saved                          | Re-open the `OPENIAM_POLICY` provider and read **DUO Authentication Provider** back; it must name the `DUO_AUTH_PROVIDER` row, not be blank                                      |
| The prompt fires because of your rule        | `/webconsole/authenticationDebug` for the test request; it names the rules that actually applied                                                                                 |
| The rule is attached where you intended      | A row in `URI_RULE_XREF` for your rule, at the `EXEC_ORDER` you set                                                                                                              |
| A Duo sign-in is distinguishable afterwards  | `LOGIN` and `DO_LOGIN` audit records for the identity at the time of the test. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) |
| An unenrolled Duo user behaves as you expect | Sign in as an identity Duo does not know. Whatever Duo does then is Duo's policy, not OpenIAM's — record it before you widen the rule                                            |

## When it goes wrong

| Symptom                                                                                     | Cause                                                                                                                                                        | Fix                                                                                                                                                                                                 |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *DUO Authentication Provider not set* on save                                               | **Supports DUO Authentication** is ticked but `AUTH_PROVIDER.DUO_PROVIDER_ID` is empty. The message key is `openiam.ui.webconsole.auth.provider.duo.not.set` | Select the `DUO_AUTH_PROVIDER` row on the policy provider                                                                                                                                           |
| No **Supports DUO Authentication** section on the provider form                             | The provider's type is not `OPENIAM_POLICY`                                                                                                                  | Put the reference on the `OPENIAM_POLICY` provider the content provider points at                                                                                                                   |
| `DUO code expired or got wrong state` on the generic message page                           | The Redis-cached `state` is gone or does not match the login, provider ID and postback URL it was cached against                                             | Confirm `DuoAuth4` is exactly `https://<OPENIAM_HOST>/idp/auth-duo-callback` and identical in the Duo admin panel; then check Redis availability. Both failures look identical from the user's side |
| Duo rejects the request before showing a prompt                                             | A trailing space or an `https://` prefix in **API hostname**; the values are passed through untouched                                                        | Re-enter `api-<id>.duosecurity.com` with no scheme and no whitespace                                                                                                                                |
| `DUO_AUTH_PROVIDER` missing from a content provider's **Authentication Provider** drop-down | That list is filtered against a fixed built-in set, not against `AUTH_PROVIDER_TYPE.IS_LINKABLE_TO_CP`                                                       | Expected. Duo is referenced from a policy provider, never attached to a content provider                                                                                                            |
| Change saved, behaviour unchanged                                                           | Provider cache sweep is 60 s                                                                                                                                 | Wait a sweep, then re-test                                                                                                                                                                          |

## Roll it back

Remove the `URI_RULE_XREF` row that attaches the rule and prove password sign-in on that path, leaving `DEF_PASSWD_AUTH_RULE` at `EXEC_ORDER` 0 alone. Then clear **DUO Authentication Provider** and untick **Supports DUO Authentication** on the policy provider. Delete the `DUO_AUTH_PROVIDER` row last, and revoke the Duo application's client secret in the Duo admin panel — deleting the OpenIAM row does not invalidate it.

## Related

* [Authentication providers and MFA](/administration-guide/security/authentication-providers-and-mfa.md) — choosing between the factors, and the break-glass recovery
* [Authentication levels, rules and capability flags](/administration-guide/security/authentication-providers-and-mfa/authentication-provider-reference.md) — `DUO_AUTH`, `SUPPORTS_DUO_AUTH` and the shipped rules
* [Step-up authentication](/administration-guide/security/step-up-authentication.md) — demanding Duo on one path only

*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/duo.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.
