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

# Add TOTP as a second factor

Turn on TOTP for an authentication policy provider, attach it to a rule, and enrol a user around OpenIAM's 60-second time step.

## What this is, and when you need it

TOTP is the code an authenticator app produces from a shared secret and the clock. In OpenIAM it is the `TOTP_AUTH` authentication level, enabled by a checkbox on an `OPENIAM_POLICY` provider and demanded by a step in an authentication rule. Enrolment is self-service: the first time a user reaches the TOTP page without a stored secret, that page shows a QR code instead of a code box.

Still choosing a factor? Start at [Authentication providers and MFA](/administration-guide/security/authentication-providers-and-mfa.md).

## At a glance

|                          |                                                                                                                                                                            |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | A test identity that must present a six-digit code after their password on one protected URL, with `LOGIN.TOTP_SECRET` populated and a `GENERATE_TOTP_SECRET` audit record |
| **Where**                | **Access Control → Authentication Providers** (`/webconsole/authenticationProviders`) and **Policy → Authentication Rule** (`/webconsole/authentication-rule-list`)        |
| **You need first**       | An `OPENIAM_POLICY` provider you can edit, a tested break-glass administrator on a route that does not carry the new rule, and one disposable test identity                |
| **Takes**                | 30–60 minutes for one provider and one narrow rule. Enrolling a population is a separate, longer exercise                                                                  |
| **Reversible**           | Partly — detaching the rule restores the previous sign-in immediately, but a secret already enrolled stays in `LOGIN.TOTP_SECRET` until you clear it                       |

## 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: a rule that demands a factor nobody has enrolled locks out everyone the rule covers, including you, and there is no in-product way back.

Then read step 5 before you enrol anybody. **OpenIAM's TOTP time step is 60 seconds**, and the QR code does not say so.

## Turn TOTP on and prove it

{% stepper %}
{% step %}

### Tick Supports TOTP Authentication on an `OPENIAM_POLICY` provider

**Access Control → Authentication Providers →** your provider. The checkbox is **Supports TOTP Authentication**. If it is not on the form at all, you are on the wrong provider type: `SUPPORTS_TOTP` ships `Y` on `OPENIAM_POLICY` and on nothing else, so put the factor on the `OPENIAM_POLICY` provider the content provider points at.

Build a new provider rather than repointing `OPENIAM_DEFAULT_AUTH`.
{% endstep %}

{% step %}

### Decide the QR label issuer, and get it changed

`org.openiam.ui.topt.issuer.name` ships **`Openiam`**. It is the name that appears above the code in the user's authenticator app, so a user with accounts at several organizations sees several entries called *Openiam* and cannot tell them apart. It should be your organization's name.

**No screen sets it.** It has no row on **Administration → System Configuration**, in the `Authentication` category or any other, and you cannot read its running value from the Webconsole either — it is supplied to each UI artifact by the deployment. Changing it is the deployment owner's job and belongs to the [Installation Guide](/installation/installation.md); what you own is deciding the value and confirming the result. The observable check is enrolment itself: when you enrol the test identity below, read the label above the code in the authenticator app.
{% endstep %}

{% step %}

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

**Policy → Authentication Rule**. The shape is `PASSWORD_AUTH` → `TOTP_AUTH` → `AUTHORIZATION_ACCEPT`: chained steps mean *and*, so this demands both. `DEF_TOTP_AUTH_RULE` ships as `TOTP_AUTH` → `AUTHORIZATION_ACCEPT` alone, which is TOTP *instead of* a password, not as well.

Attach your rule to one test URI pattern at `/webconsole/editProviderPattern`. Never to `DEFAULT_CP` on a first test: a rule on a content provider applies to everyone that provider serves.
{% endstep %}

{% step %}

### Wait a sweep

`org.openiam.auth.provider.sweep.time` is `60000` ms. Testing inside that minute tells you nothing.
{% endstep %}

{% step %}

### Enrol the test identity, with the period set to 60

{% hint style="danger" %}
**OpenIAM's TOTP time step is 60 seconds, not the RFC 6238 default of 30.** The step is fixed at 60000 ms; no field or property changes it. The `otpauth://` URI in the enrolment QR code carries only `secret` and `issuer` — **no `period` parameter** — so authenticator apps fall back to 30 seconds and produce a code the server rejects roughly half the time, in a pattern that looks like clock drift. Users must enter the secret manually with the period set to 60, or use an app that lets them edit the period after scanning.

Confirm it before you blame the clock: fetch the enrolment page metadata (under [Verify it worked](#verification)) and read the `otpauth://` URI out of the response — there is no `period=` in it. The 60-second step itself is not exposed anywhere in the console; only OpenIAM support can confirm the server-side value.
{% endhint %}

Sign in as the test identity and request the protected URL. The TOTP page is `https://<OPENIAM_HOST>/idp/auth-totp`, and because the identity has no stored secret it renders an enrolment QR code.

**Do not scan it.** Read the secret off the page and add the account to the authenticator app manually, choosing a **60-second** period. An app that cannot be told the period will produce codes that fail about half the time.

If the QR image is blank, the deployment has no outbound HTTP: the image itself is rendered by `https://chart.googleapis.com/chart?...`. The secret is still on the page, and validation needs no outbound call.
{% endstep %}

{% step %}

### Submit one code, then sign in again

Enter one code on the TOTP page and confirm it is accepted. Then sign out and sign in a second time, to confirm the *stored* secret validates and not just the freshly generated one — OpenIAM checks those two differently, and the second is the one your population will meet every day.
{% endstep %}
{% endstepper %}

## Verification

| What to check                             | How                                                                                                                                                                                                                                                                                                                                            |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The secret was stored                     | `LOGIN.TOTP_SECRET` is non-null and `LOGIN.TOPT_ACTIVE='Y'` for the test identity's principal on the provider's managed system. Note the transposed spelling in the second column name                                                                                                                                                         |
| Enrolment happened                        | A `GENERATE_TOTP_SECRET` audit record for the identity. That record is the only proof a user actually enrolled                                                                                                                                                                                                                                 |
| The prompt is firing because of your rule | `/webconsole/authenticationDebug` reports how the request was evaluated and names the rules that applied                                                                                                                                                                                                                                       |
| The rule is attached where you intended   | A row in `URI_RULE_XREF` (or `CP_RULE_XREF`) for your rule, at the `EXEC_ORDER` you set                                                                                                                                                                                                                                                        |
| The enrolment URI really omits the period | Decode the QR image on the enrolment page. The `otpauth://` value inside it carries `secret` and `issuer` and **nothing else** — no `period` — which is why the authenticator app assumes 30 seconds while OpenIAM uses 60. Where the QR image is blank, the same absence shows up in the authenticator app's own view of the imported account |

## When it goes wrong

| Symptom                                                                                                                             | Cause                                                                                                                                                              | Fix                                                                                                                                        |
| ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Codes rejected about half the time; looks like clock drift                                                                          | Server time step is 60 s and the enrolment URI omits `period`                                                                                                      | Re-enrol with the period set to 60 in the authenticator app                                                                                |
| No **Supports TOTP Authentication** checkbox on the provider form                                                                   | `AUTH_PROVIDER_TYPE.SUPPORTS_TOTP='N'` for that type — shipped `Y` only for `OPENIAM_POLICY`                                                                       | Put the factor on the `OPENIAM_POLICY` provider the content provider points at                                                             |
| `404` with `TOTP_NOT_ENABLED_FOR_PROVIDER` on the TOTP page                                                                         | Either `AUTH_PROVIDER.SUPPORTS_TOTP` is off, or the URI pattern requires no auth levels at all. The same error is returned for both                                | Check both before concluding either                                                                                                        |
| Enrolment fails only behind a load balancer; the first code from every new enrolment is rejected, while enrolled users keep working | The freshly generated secret is held in the HTTP session between rendering the QR code and validating the first code, and the two requests reached different nodes | A deployment fix, not a product setting — [Installation Guide](/installation/installation.md), session affinity and shared session storage |
| QR image blank                                                                                                                      | The image is fetched from `chart.googleapis.com`                                                                                                                   | Enter the secret manually; no outbound HTTP is needed for validation                                                                       |
| Configured but never prompts                                                                                                        | No `RULE_AUTHENTICATION_STEP` for the `TOTP_AUTH` grouping in the rule attached to that path                                                                       | Add the step and an edge to `AUTHORIZATION_ACCEPT`, then check `CP_RULE_XREF` / `URI_RULE_XREF`                                            |
| Change saved, behaviour unchanged                                                                                                   | Provider cache sweep is 60 s, policy cache sweep 72 s                                                                                                              | Wait a sweep, then re-test                                                                                                                 |

## Roll it back

Remove the `URI_RULE_XREF` row that attaches your rule and prove password sign-in on that path again, leaving `DEF_PASSWD_AUTH_RULE` at `EXEC_ORDER` 0 alone. Wait out the 60-second sweep before you retry. Clearing **Supports TOTP Authentication** on the provider stops new enrolments but does not remove `LOGIN.TOTP_SECRET` from identities that already enrolled; re-enabling it later re-uses those secrets.

There is no per-user "reset TOTP" action in the Webconsole. Clearing an individual secret is a break-glass update to `LOGIN.TOTP_SECRET` and `LOGIN.TOPT_ACTIVE`, which produces no audit record and validates nothing — restore a sign-in path and have the user re-enrol wherever that is possible.

## What is fixed, and what is not

| Item                        | Where it lives                                                                       |
| --------------------------- | ------------------------------------------------------------------------------------ |
| Per-identity secret         | `LOGIN.TOTP_SECRET`, `VARCHAR(100)`                                                  |
| Per-identity enrolment flag | `LOGIN.TOPT_ACTIVE`                                                                  |
| Secret generation           | Server side, when the QR code is rendered. Not configurable                          |
| Validation window           | Server side against the stored secret. There is no per-provider skew or window field |
| Time step                   | 60 seconds, fixed                                                                    |
| QR label issuer             | `org.openiam.ui.topt.issuer.name`, shipped `Openiam`                                 |

## What changes in 2026.9.1

**Everything above describes 2026.8.1 and is accurate for it.** The three constraints that make enrolment awkward — the 60-second step, the `period`-less URI and the externally rendered QR image — are lifted in 2026.9.1, and the procedure changes with them. Do not apply this section to a 2026.8.1 deployment.

| On 2026.8.1                                                              | From 2026.9.1                                                                                                                  |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Time step 60 seconds, fixed                                              | 30 seconds — the RFC 6238 default                                                                                              |
| The `otpauth://` URI carries `secret` and `issuer` only                  | A standard URI: 30-second period, six digits, SHA-1                                                                            |
| Users must add the account manually with the period set to 60            | Users scan the QR code                                                                                                         |
| QR image rendered by `chart.googleapis.com`; blank with no outbound HTTP | Rendered by the OpenIAM server and embedded in the page — no external request, and the secret never leaves your infrastructure |
| Third-party apps produce codes the server rejects about half the time    | Any RFC 6238 app works: Microsoft Authenticator, Google Authenticator, Authy, and the OpenIAM Authenticator                    |

Once a deployment is on 2026.9.1, enrolment happens by itself the first time a user reaches a TOTP-protected URL:

1. The user signs in and reaches the two-factor screen, which shows a QR code and a six-digit code field.
2. They add an account in their authenticator app by scanning it — **Add account** > **Other account** in Microsoft Authenticator, **Add a code** > **Scan a QR code** in Google Authenticator.
3. They type the current code and continue.

That first accepted code completes the enrolment: the secret is stored against the login, and from then on the same screen asks only for a code — the QR code is not shown again. To let someone enrol a replacement device, clear the stored secret for their login so that the QR code is presented once more.

**Device clock drift still applies.** Codes are derived from the current time on both sides with no exchange between them, so a device more than about a minute out produces codes that are rejected; most authenticator apps offer a "correct for time" option. On 2026.8.1, rule out the 60-second step above first — it produces a very similar symptom.

{% hint style="info" %}
**Still not My Devices.** The **My Devices** screen registers the OpenIAM Authenticator app for push-notification approval, using a different QR code in a different format — an `https` pairing URL rather than an `otpauth://` URI. A third-party authenticator app cannot be registered there, and does not need to be. That flow has no article in this guide yet.
{% endhint %}

## 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) — `TOTP_AUTH` among every level, and the shipped rules
* [Adaptive authentication](/administration-guide/security/adaptive-authentication.md) — demanding TOTP only when the sign-in looks unusual

*Applies to OpenIAM 2026.8.1. Last reviewed 1 September 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/totp.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.
