> 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/federation/jit-provisioning.md).

# Just-in-time provisioning

Create an OpenIAM identity on an external user's first federated sign-in, with the gating attribute, the fixed creation order and the tests that prove an unapproved person is refused.

## What this is, and when you need it

Just-in-time provisioning creates an OpenIAM identity when an approved external user authenticates through a SAML trust for the first time, and can update that identity on later sign-ins. It removes the need to pre-stage an external population, and because nothing is created until somebody signs in, it leaves fewer orphaned accounts behind. It also moves identity creation into the authentication transaction, which is why it is a governed change rather than a convenience setting.

JIT does **not** detect leavers who simply stop signing in, reconcile a population, prove a person is unique, or safely turn an external `admin` claim into OpenIAM privilege — see [Choose JIT, synchronization or registration](#choose-jit-synchronization-or-registration).

## At a glance

|                          |                                                                                                                                                                                                |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | One `SAML_SP_PROVIDER` on which an approved unknown user's first sign-in creates exactly one identity with the type, status and role you chose — and an unapproved issuer creates nothing      |
| **Where**                | **Access Control → Authentication Providers →** your SAML SP provider, customization section, field **Just-in-time provisioning groovy script**                                                |
| **You need first**       | A finished SAML SP trust with JIT off and an unknown user provably rejected; a reviewed Groovy script; and to know whether provisioning is enabled here                                        |
| **Takes**                | A day for a first trust. The script is the work; the field is one selection                                                                                                                    |
| **Reversible**           | **Clearing the attribute stops new state immediately, but identities already created are real** and may own real accounts. Remove them through lifecycle controls, not by undoing this setting |

## Before you begin

Finish the trust first with JIT **off** — [OpenIAM as a SAML service provider](/administration-guide/federation/saml-service-provider.md) — with one correlated user working and an unknown user provably rejected with *"Could not find principal '%s' using managed System '%s"*. That rejection is your baseline, and how you tell "JIT never ran" from "JIT ran and failed".

There are **two** JIT mechanisms, and the capability flag belongs to the one that does not work.

{% hint style="danger" %}
**Ticking Supports Just In Time Authentication does nothing useful.** `AUTH_PROVIDER.SUPPORTS_JUST_IN_TIME_AUTH` is a `CHAR(1)` present since 4.0.0.0, and its type-level flag is true for `SAML_SP_PROVIDER` only — which is why the checkbox is rendered here and looks like the setting you want. It does not gate the SAML service. It gates the login module, and **no shipped login module implements the JIT hook it calls**, so an unknown user on that path fails with an `IllegalStateException` carrying *"createUserForJustInTimeAuthentication() should be overridden by the login module"* rather than producing an identity. That message in the ESB log is the unambiguous sign you enabled the flag instead of selecting a script. Filling the flag in usefully is a **Developer Guide** task, not configuration.
{% endhint %}

The mechanism that works is the **`SamlSpJITGroovyScript`** attribute. A script ID present means JIT is on; absent means off. There is no second setting, and an unknown user stops being rejected the moment you save.

Also establish whether **provisioning is enabled** here. With it on, a JIT-created user is saved *through* provisioning, so business rules, policy maps and connectors all fire inside the authentication request — a first sign-in can create real accounts in real managed systems. You cannot read that setting from the Webconsole; the deployment owner knows it.

## Configure it

{% stepper %}
{% step %}

### Decide the resource rights before anybody signs in

`org.openiam.saml.sp.just.in.time.resource.rights` ships **`READ`**. It takes comma-separated access-right IDs, each of which must resolve, and a bad value fails the first JIT sign-in with *"Cannot access rights: %s. Response: %s"* rather than granting nothing quietly — which is the behaviour you want.

It is an IdP property and takes effect on restart, so a change belongs to the deployment owner and the [Installation Guide](/installation/installation.md), not to this screen. It does not appear under **Administration → System Configuration** and you cannot read its running value from the Webconsole. What you own is deciding what the rights should be, and confirming afterwards: open a JIT-created user and read the access rights on the SAML SP provider's resource. If `READ` is what you wanted, there is nothing to change.
{% endstep %}

{% step %}

### Write and register the script

Extend `AbstractJustInTimeSAMLAuthenticator`. Four methods are abstract and you must implement all four; three more are optional overrides, and one of those decides whether a returning user is updated at all. The full contract, a script that compiles, and the claim names each identity provider actually sends are in [The JIT script contract and claim mapping](/administration-guide/federation/jit-provisioning/jit-provisioning-reference.md).

Put the rejections in `getPrincipal()`, `getFirstName()` and `getLastName()` — those run *inside* the creation sequence, so throwing there prevents the identity being built at all. That is the only way to refuse before creation, because entitlement is checked afterwards.

Register the script per [Create a Groovy script](/administration-guide/automation/manage-groovy-scripts.md).
{% endstep %}

{% step %}

### Discover the claim names this identity provider really sends, before you rely on them

Do not guess and do not copy a name out of somebody else's script. `urn:oid:*` names are right for a generic SAML identity provider and **wrong for Entra ID**, which sends WS-\* claim URIs and untyped attribute values. Three ways to find out, best first, are on [The JIT script contract and claim mapping](/administration-guide/federation/jit-provisioning/jit-provisioning-reference.md#discover-the-names-your-provider-really-sends).
{% endstep %}

{% step %}

### Select the script on the service provider

On the SAML SP provider's customization section, set **Just-in-time provisioning groovy script** (`SamlSpJITGroovyScript`). **This is the moment JIT becomes live**, with no separate confirmation and no other switch to throw.

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-1a52877de16b0f269a03d71bf94a6fdc983983d8%2FEntraID-01-JIT-groovy.png?alt=media" alt="The Customization section of a SAML provider with the just-in-time provisioning Groovy script path set to /idp/AzureJustInTimeSAMLAuthenticator.groovy"><figcaption></figcaption></figure>

Keep the provider attached only to a narrow non-production route until you have run the unknown-user test.
{% endstep %}

{% step %}

### Use an external test identity that cannot trigger anything expensive

One that no birthright rule matches and whose organization has no provisioning-heavy roles. With provisioning enabled, a role added in your script fires business rules, policy maps and connectors inside the authentication request.
{% endstep %}

{% step %}

### Wait a sweep, then work the verification matrix

`org.openiam.auth.provider.sweep.time` is `60000` ms. Then run every row of [Verify it worked](#verification) — the negative rows are the point of the exercise.
{% endstep %}
{% endstepper %}

## Verification

| Case                                                    | Required evidence                                                                                                                                                                                                                                                             |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Existing one-match user                                 | Authentication succeeds; only the fields your `updateUser` touches changed                                                                                                                                                                                                    |
| Approved unknown user                                   | Exactly **one** identity and one principal created, with the intended type, organization, status, owner and expiry; the SAML provider resource granted with the configured rights; no unintended downstream provisioning                                                      |
| Unapproved issuer or domain                             | **No** identity created. Your rejection inside `getPrincipal()` fired                                                                                                                                                                                                         |
| Missing required attribute                              | No identity created                                                                                                                                                                                                                                                           |
| Duplicate or case-variant principal                     | Fails closed. No merge, no overwrite                                                                                                                                                                                                                                          |
| Same email from two issuers                             | Two distinct identities. No cross-person correlation                                                                                                                                                                                                                          |
| Assertion claiming a group outside the allow-list       | The group is **not** granted; the sign-in still succeeds. Confirm from the membership audit, not from the screen                                                                                                                                                              |
| Attribute names on a new trust                          | First and last name on the created identity are the real ones, not nulls and not random strings — the proof that you read the provider's actual claim names                                                                                                                   |
| Disabled or expired OpenIAM user                        | Remains denied                                                                                                                                                                                                                                                                |
| Later sign-in with an omitted optional attribute        | The stored value is **not** erased                                                                                                                                                                                                                                            |
| Untrusted issuer, wrong audience, bad signature, replay | Rejected by the SP trust *before* JIT                                                                                                                                                                                                                                         |
| Provisioning failure                                    | `AuthenticationException` with `SAML_SECURITY_EXCEPTION`; no orphaned or partially privileged identity                                                                                                                                                                        |
| Unentitled JIT user                                     | Created, then refused with `SAML_USER_NOT_AUTHORIZED_TO_SSO`. Confirm that is acceptable, because it is the shipped order                                                                                                                                                     |
| Audit                                                   | `SAML_SP_PROCESS_SAML_RESPONSE` carrying the `JUST_IN_TIME_AUTH` attribute set to `true` — that is how you tell a JIT-created session from a correlated one after the fact. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) |

## When it goes wrong

| Symptom                                                                                            | Cause                                                                                                                                                                                                     | Fix                                                                                                                                                                                                         |
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ticking **Supports Just In Time Authentication** does nothing, or throws                           | No shipped login module implements that hook — look for *"createUserForJustInTimeAuthentication() should be overridden by the login module"* in the ESB log                                               | Use `SamlSpJITGroovyScript`. That flag needs a custom login module                                                                                                                                          |
| Unknown users are suddenly created and you did not turn anything on                                | A script ID in `SamlSpJITGroovyScript` **is** the switch                                                                                                                                                  | Clear the attribute                                                                                                                                                                                         |
| *"Implementing groovy script must return a non-null value from getEmailType()"*                    | `getEmailType` returned null, or a type that is not in the list it was handed                                                                                                                             | Return a member of the list — the types grouped under `EMAIL` in your deployment's metadata                                                                                                                 |
| *"Cannot access rights: …"* at create time                                                         | `org.openiam.saml.sp.just.in.time.resource.rights` names an access-right ID that does not resolve                                                                                                         | The property is an IdP property change and a restart — [Installation Guide](/installation/installation.md). The shipped value is `READ`                                                                     |
| *"The return value from the provisioning service did not have any principals."*                    | Provisioning stripped or failed to persist the principal                                                                                                                                                  | Check the policy map and the provisioning log                                                                                                                                                               |
| *"… AFTER Just-in-time provisioning"*                                                              | The created principal is not findable on the provider's managed system. That suffix is what distinguishes this failure from the pre-JIT lookup                                                            | Confirm the login was built on the managed system you expect, and that provisioning did not alter the login value                                                                                           |
| *"Just in time provisioning failed…"*                                                              | Provisioning is enabled and a business rule, policy map or connector failed                                                                                                                               | Diagnose the provisioning failure; do not disable provisioning to hide it                                                                                                                                   |
| A JIT user is created and then refused SSO                                                         | Entitlement is checked **after** creation                                                                                                                                                                 | Grant the resource, or reject inside the script                                                                                                                                                             |
| Duplicate identities for one person                                                                | Unstable or case-sensitive principal                                                                                                                                                                      | Namespace and lower-case it; remediate existing duplicates under identity policy before fixing the script                                                                                                   |
| Two people merged into one identity                                                                | Email used as the correlation key across issuers                                                                                                                                                          | Correlate on `{issuer, subject}`                                                                                                                                                                            |
| Returning users are never updated                                                                  | `updateUser` is empty unless you override it                                                                                                                                                              | Override it, conservatively                                                                                                                                                                                 |
| First and last name are null, or the sign-in is refused for a missing given name, against Entra ID | The script reads `urn:oid:*` names; Entra sends `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/*`                                                                                                 | Use the provider's claim names — [Attribute names differ by identity provider](/administration-guide/federation/jit-provisioning/jit-provisioning-reference.md#attribute-names-differ-by-identity-provider) |
| `ClassCastException` inside the creation sequence on an Entra trust                                | The claim name is right but the value was cast to `XSString`; Entra's values are untyped                                                                                                                  | Read the value with `getTextContent()`, or use the type-agnostic helper on the reference page                                                                                                               |
| Groups in the assertion are ignored and the user gets none                                         | The claim name does not match, the group name is not an exact match, the search is not scoped to the right managed system, or Entra sent group object IDs rather than names                               | Confirm the claim name and the stored group names; set the managed system; configure the claim to send names                                                                                                |
| A JIT user got administrator access                                                                | An external role or group claim was mapped into OpenIAM, or a birthright rule matched the created identity                                                                                                | Remove the mapping; review birthright rules against the created type and organization. Map only from an allow-list                                                                                          |
| A random-named test user appeared in production                                                    | The shipped `idp/TestJustInTimeSAMLAuthenticator_1.0.groovy` was selected — it returns a five-character random string from both name methods, so you will recognise it by users named things like `k7f2q` | Replace the script; remediate the identity                                                                                                                                                                  |
| JIT users accumulate after people leave                                                            | Login-time JIT cannot observe departures                                                                                                                                                                  | Sponsor review or authoritative reconciliation, plus certification                                                                                                                                          |

## Roll it back

Clear `SamlSpJITGroovyScript` **first**, then confirm unknown users are rejected again with *"Could not find principal"* while existing users still authenticate. That is the single reversal that stops new state being created.

Then correct or disable the test identities and any downstream accounts through normal lifecycle controls. **Do not bulk-delete JIT-created users** until access, target accounts, audit retention, legal hold and ownership have been reviewed — a JIT-created identity may own real accounts in real systems by the time you notice.

## "Just-in-time" here means account creation, not privileged access

The term is overloaded. In OpenIAM, JIT provisioning creates a *user record* the first time somebody arrives from a trusted identity provider. It is not just-in-time *access elevation*, and OpenIAM does not offer **zero standing privilege**: entitlements are granted and revoked, with optional start and end dates, but nothing hands an entitlement out for the duration of a task and takes it back automatically. If a control objective asks for time-boxed privileged access, build it from a request with an end date and a certification campaign, and say plainly that it is not automated expiry.

## Choose JIT, synchronization or registration

| Requirement                                                                  | Better fit                                                       |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Small approved external population; the identity is only needed at first use | SAML JIT with strict issuer and population controls              |
| Complete joiner/mover/leaver coverage before anyone signs in                 | Authoritative synchronization, SCIM or a connector               |
| The person must supply missing data, consent, a sponsor or approval          | Self-registration workflow                                       |
| Workforce identities already exist in OpenIAM                                | Correlate existing users; leave JIT off                          |
| Privileged administrators, or anyone with birthright provisioning            | Pre-provision and review. Do not create privilege at first login |

## What the base class does, in order

The creation sequence is fixed. `createUser()` is `final`: you cannot override it, only supply its inputs. Most JIT surprises are the order of these steps rather than the script.

| Order | What happens                                                                                                                                     | What it means for you                                                                                                                                                                                                    |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1     | The SP trust is enforced — issuer, audience, signature, time conditions                                                                          | An untrusted issuer or a bad signature never reaches your script                                                                                                                                                         |
| 2     | The NameID is resolved to a principal (the resolver is told JIT is on) and looked up on the service provider's managed system                    | With JIT off a miss refuses the sign-in with *"Could not find principal '%s' using managed System '%s"*. With JIT on it proceeds to your script instead                                                                  |
| 3     | No match runs your `createUser()` inputs; a match loads the existing user in full and passes it to `updateUser(user)`                            | `updateUser` is empty unless you override it, so a returning user is left exactly as they were by default                                                                                                                |
| 4     | On create, four things are populated in a fixed order: first name, last name, the login, the email address — and only then your `populate(user)` | The login is built with the **service provider's** managed system and whatever `getPrincipal()` returns. The email address uses the metadata type `getEmailType()` returns, and returning null fails the sign-in         |
| 5     | The SAML provider's own resource is granted, with the access rights named by `org.openiam.saml.sp.just.in.time.resource.rights` (ships `READ`)   | The base class grants exactly the entitlement needed to use this trust and nothing else. That is a good default, and it is also why a JIT user can sign in but reach nothing else                                        |
| 6     | The user is saved — **through provisioning** if provisioning is enabled on the deployment, otherwise the principal only                          | With provisioning enabled, business rules, policy maps and connectors fire inside the authentication request, and a failure refuses the sign-in with `SAML_SECURITY_EXCEPTION`                                           |
| 7     | The principal is re-read and looked up again. The saved user must come back with at least one principal                                          | A miss fails with a message ending *"…AFTER Just-in-time provisioning"* — that suffix distinguishes it from step 2                                                                                                       |
| 8     | **Only then is entitlement checked**                                                                                                             | **The identity is created before entitlement is evaluated.** A user your access policy would refuse is still created, and then refused, so a rejected sign-in still leaves a user behind in **User Admin → User Search** |

To refuse *without* creating, your script must reject inside the inputs step 4 calls — throw from `getPrincipal()`, `getFirstName()` or `getLastName()` — rather than relying on entitlement.

## Safe attribute handling

* Read attributes by exact agreed SAML name and expected type. Cast deliberately — the name *and* the type differ by provider.
* Require one value where the contract is single-valued; refuse an ambiguous multi-value rather than taking the first one.
* Normalise only by a documented rule. Never silently truncate an identity key.
* Namespace the principal by issuer or tenant. **Email is not a safe global correlation key**: two trusted issuers can assert the same address for different people, and people change addresses.
* Set the status intentionally. Do not create an active identity merely because authentication succeeded.
* Keep `updateUser` conservative. The default is a no-op, and a bad override that clears a good OpenIAM value on every login is worse than no update.
* **Never map an external role or group string onto an OpenIAM administrator role.** Map from an explicit allow-list, keep administrative groups and roles out of the mapping entirely, and route privilege through request and approval ([Configure requests and approvals](/administration-guide/requests.md)).
* Map on create only. Leaving group mapping out of `updateUser` means a later assertion cannot add access — a deliberate trade, since it also means removals in the IdP do not propagate, which is reconciliation's job.
* Audit what the mapping granted. The evidence question is not "did JIT run" but "which entitlements did an external assertion cause". Put JIT-created identities in a certification campaign ([Govern access](/administration-guide/governance.md)) so an owner re-confirms the mapped groups on a schedule.
* Do not log complete assertions, tokens or unnecessary PII. A decoded assertion is PII and a bearer credential.

## Worked example: one partner tenant

Objective: permit exactly one partner IdP; correlate on `{issuer, immutable subject}`; require given name, surname and an in-domain email; create a non-active `External Partner` identity in one organization with an expiry; grant only the SAML provider resource with `READ`; escalate any additional access to a sponsor.

| Decision            | Value                                                                                                                  |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Trusted issuer      | `https://idp.partner.example.com/saml2`, enforced in `getPrincipal()`                                                  |
| Principal           | `partner:<lower-cased NameID>` — namespaced, so a second tenant cannot collide                                         |
| Required attributes | Given name, surname and email, read by the claim names this IdP actually sends                                         |
| Domain restriction  | Email must end `@partner.example.com`                                                                                  |
| Created status      | An explicit non-active status set in `populate()`                                                                      |
| Created user type   | `External Partner`, set as the user type in `populate()`                                                               |
| Initial role        | One non-privileged portal role, resolved by name rather than a hard-coded ID                                           |
| Group mapping       | From a two-entry allow-list, resolved by exact name on the partner managed system; no administrative group is mappable |
| Resource rights     | `org.openiam.saml.sp.just.in.time.resource.rights` = `READ`                                                            |
| Update policy       | Email and given name only, and never blanked                                                                           |
| Additional access   | Sponsor request, never a claim                                                                                         |

The complete script for this example is on [The JIT script contract and claim mapping](/administration-guide/federation/jit-provisioning/jit-provisioning-reference.md).

The multi-tenant version changes one thing and one thing only: correlate on `{issuer, subject}` with a per-issuer prefix, map each approved issuer to its own organization and sponsor policy, and **deny unknown issuers**. Test the same email arriving from both issuers and confirm two distinct identities.

Because login-time JIT cannot see departures, a periodic sponsor or authoritative reconciliation is part of the design, not an optional extra.

## Related

* [The JIT script contract and claim mapping](/administration-guide/federation/jit-provisioning/jit-provisioning-reference.md) — the method contract, a script that compiles, the per-provider claim names and group mapping
* [OpenIAM as a SAML service provider](/administration-guide/federation/saml-service-provider.md) — the trust JIT extends
* [Self-registration](/administration-guide/identities/self-registration.md) — when the person must supply data and consent
* [Govern access](/administration-guide/governance.md) — certifying JIT-created identities

*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/federation/jit-provisioning.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.
