
Implementing Just-in-Time Provisioning with SAML and OIDC
A technical guide on configuring just-in-time provisioning using SAML and OIDC with SCIM for automated identity management.
Just-in-Time (JIT) provisioning resolves the architectural inefficiency of maintaining redundant directories for identical user sets. In traditional models, organizations rely on scheduled cron jobs, such as Active Directory synchronization scripts running every 15 minutes, to push user data to SaaS applications. This approach introduces failure points where delayed or failed syncs block user access. JIT provisioning eliminates these periodic jobs by shifting the state transition to the exact moment of authentication. The Identity Provider (IdP) acts as the source of truth, using SAML or OIDC for authentication and SCIM for identity synchronization, triggering user creation only upon the first successful assertion.
The core mechanism relies on a specific race condition managed by the Service Provider (SP). The IdP does not know if the user exists in the SP until the login attempt occurs. When credentials are presented, the IdP validates them and sends an assertion. The SP receives this assertion, extracts the unique identifier, checks its local database, and if the identifier is missing, it initiates a provisioning event to create the account before granting access. This is not magic; it is a deterministic callback logic that ensures the user record exists before the session is established.
The Mechanism of Trigger
The fundamental trigger for JIT provisioning is the "first-hit" logic. When a user attempts to log in, the IdP intercepts the authentication flow and validates the credentials against its internal directory. If valid, the IdP prepares an assertion containing the user's identity attributes. The SP receives this assertion and performs a lookup in its own user store.
If the user is found, the standard authentication flow proceeds. If the user is not found, the SP does not reject the login immediately. Instead, it pauses the session creation process. This pause allows the system to extract necessary attributes from the incoming assertion (such as firstName, lastName, or department) and execute a provisioning script. This script creates a new user record in the SP's database using the data provided in the assertion. Only after the record successfully exists does the SP grant the user access. This ensures that the identity creation is tightly coupled with the authentication event, removing the need for external synchronization loops.
The SAML JIT Trigger
In SAML (Security Assertion Markup Language), the flow is defined by the XML envelope. The critical artifact here is the <NameID> element within the <Subject> block. This element contains the unique identifier for the user, often an email address or a GUID.
Consider an actor named "Alice" logging into "Salesforce" via "Keycloak" (the IdP).
- Alice enters her credentials at Keycloak.
- Keycloak validates Alice against its internal database.
- Keycloak generates a SAML Response. Inside the
<Assertion>, there is a<Subject>containing<NameID>alice@example.com</NameID>. - This XML is signed and POSTed to Salesforce's Assertion Consumer Service (ACS) URL.
Salesforce receives the XML. Its logic parses the <NameID>.
- Scenario A: Salesforce has a user with
alice@example.comin its user table. It creates a session. - Scenario B: Salesforce does not find
alice@example.com. This is where JIT triggers.
In a JIT configuration, Salesforce does not reject the login. Instead, it pauses the session creation, extracts the attributes from the <AttributeStatement> (e.g., firstName, lastName, department), and executes a provisioning script. This script creates a new user record in the Salesforce database using the data from the SAML assertion. Only after the record exists does Salesforce grant Alice access.
The configuration in the IdP (like Keycloak) requires enabling "Create User" or "JIT Provisioning" for the specific client (Salesforce). The IdP must map the SAML attributes to the SP's expected schema. If the IdP does not send the required attributes (like email), the SP cannot create the user, and the login fails.
The OIDC JIT Flow
OpenID Connect (OIDC) operates on JSON, making the mechanism slightly more explicit but logically similar. The unique identifier here is the sub (subject) claim in the ID Token.
Let's trace the flow for "Bob" logging into "Jira" via "Auth0".
- Bob initiates the OAuth2 authorization request.
- Auth0 authenticates Bob.
- Auth0 issues an ID Token (a JWT). This token contains
"sub": "auth0|123456"and"email": "bob@company.com". - Jira receives the token via the
userinfoendpoint or directly from the authorization code response.
Jira parses the JWT. It extracts the sub claim.
- Jira queries its database for
auth0|123456. - If not found, Jira invokes its JIT logic.
Unlike SAML, where the attributes are embedded in the XML response, OIDC often relies on the scope and the userinfo endpoint to fetch additional claims. However, for JIT, the sub is the primary key. The SP (Jira) must be configured to map the sub to its internal user ID.
A common pitfall in OIDC JIT is the lack of mutable attributes. Once the user is created, the sub rarely changes. If Bob's email changes in the IdP, the SP needs a mechanism to update the profile. In SAML, this happens via a new assertion with the updated email. In OIDC, the SP must listen for a token refresh or a specific userinfo call to detect attribute drift.
The SCIM Bridge
Relying solely on SAML or OIDC attributes for user creation is fragile. You need a standardized protocol to ensure the data structure is consistent. That protocol is SCIM (System for Cross-domain Identity Management).
While SAML/OIDC handle authentication (who are you?), SCIM handles provisioning (who do you look like in our system?). In a well-architected JIT implementation, the SP does not blindly trust the SAML/OIDC payload to create the user. Instead, the SP uses the data from the assertion to trigger a SCIM POST request to the IdP, or the IdP pushes a SCIM POST to the SP.
The most common architecture is the "Pull" model where the IdP is the Source of Truth:
- The SP receives the SAML/OIDC assertion.
- The SP sees the user is missing.
- The SP constructs a SCIM
POSTrequest to the IdP's SCIM endpoint (e.g.,https://idp.example.com/scim/v2/Users) to verify or retrieve the user record. - The IdP receives this request, validates the user in its directory, and returns the user details via a SCIM
GETor201 Createdresponse. - The SP then uses this validated data to create the user locally in its own database.
Alternatively, in a "Push" model, the IdP may proactively send a SCIM POST to the SP immediately after authentication, creating the user record in the SP's system based on the assertion.
This decoupling is crucial. If the IdP changes its internal schema, the SP only cares about the SCIM interface. The SAML/OIDC flow is just the trigger.
In Keycloak specifically, when acting as an IdP for a SP that supports JIT, Keycloak authenticates the user. The SP (e.g., Salesforce) is the component that creates the user in the SP's system based on the assertion. Keycloak does not create users in the SP's database. Keycloak's "User Federation" or custom SPI handles the authentication logic, ensuring the IdP remains the authoritative source.
However, the direction of trust matters. If the SP is the source of truth, the IdP should not create users. If the IdP is the source of truth, the SP should create users based on IdP assertions. The most secure pattern is "IdP as Source of Truth." The IdP creates the user record, and the SP syncs it via SCIM. This prevents "orphaned" users in the SP who can no longer be authenticated because their IdP record was deleted.
Common Pitfalls
Implementing JIT requires navigating several specific technical traps:
- Immutable
subClaims in OIDC: Thesubclaim in OIDC must remain constant for a specific user identity. If the IdP generates a newsubfor the same user (e.g., due to a migration or re-linking of accounts), the SP will treat this as a new entity, creating a duplicate user account. This leads to data fragmentation where the user appears twice in the application with different histories. - NameID Format Mismatches: In SAML, the
<NameIDFormat>attribute dictates how the identifier is interpreted. If the IdP sends aNameIDformat ofEmailAddressbut the SP expectsUnspecifiedorKerberos, the lookup logic will fail. The SP cannot find the user, and the JIT trigger will not fire, resulting in a denied login even though the user exists in the IdP. - Latency Implications: JIT introduces a small delay (milliseconds to seconds) during the first login while the user object is being created. This is acceptable for human users but can be problematic for high-frequency automated scripts or service accounts that do not expect the initial handshake to involve a provisioning step.
Practical Takeaways
To ensure a successful JIT deployment, consider these mental models:
- Trust Boundaries: Always assume the IdP is the source of truth. The SP should never be the primary writer of identity data unless explicitly designed as a federated write-back scenario.
- Identifier Consistency: The unique identifier used in the authentication token (
NameIDorsub) must map 1:1 with the unique identifier used in the provisioning payload (userNamein SCIM). - Graceful Degradation: Design your application to handle the "user not found" state gracefully. The provisioning step is an internal side effect, not a public-facing error state.
FAQ
Q: Can I use JIT provisioning without SCIM? A: Yes, but it is highly discouraged. Without SCIM, you must manually parse and map every attribute from the SAML/OIDC assertion to your application's database schema. This approach is brittle and prone to breaking whenever the IdP changes its attribute naming conventions.
Q: What happens if the IdP deletes a user while they are active in the SP? A: In a standard JIT setup, the SP creates a local copy of the user data. Deleting the user in the IdP does not automatically delete them in the SP. You typically need a separate "deprovisioning" mechanism or a scheduled sync to remove orphaned accounts from the SP.
Q: Does JIT work with multi-factor authentication (MFA)? A: Yes. The JIT provisioning logic triggers after the IdP successfully validates the user (including any MFA steps) and generates the assertion. The SP creates the user before the session is finalized, so MFA does not interfere with the provisioning flow.
Configuration Trade-offs
Implementing JIT requires careful configuration of the attribute mapping. In SAML, you must ensure the <AttributeStatement> includes the NameID and any required profile attributes. If the IdP sends a NameID format of EmailAddress but the SP expects Unspecified, the lookup will fail, and the JIT trigger will not fire.
In OIDC, the sub claim must be immutable. If the IdP generates a new sub for the same user (e.g., due to a migration), the SP will create a duplicate user account, leading to data fragmentation.
There is also a latency trade-off. JIT introduces a small delay (milliseconds to seconds) during the first login while the user object is being created. This is acceptable for most enterprise applications but can be problematic for high-frequency automated scripts that do not expect the first run to succeed.
For environments with strict compliance requirements, relying purely on SAML/OIDC attributes for user creation is risky. The SCIM bridge is mandatory. It ensures that the data transferred is validated against the schema defined in RFC 7643 and that the user object is created with the correct attributes, regardless of how the IdP formats its internal data. Without SCIM, you are building a custom parser for every attribute in every assertion, which is brittle and hard to maintain.
The mechanism is simple: Authenticate -> Check Existence -> Create if Missing -> Grant Access. The complexity lies in ensuring the unique identifiers match across the SAML/OIDC tokens and the SCIM payloads. If the NameID in SAML does not match the userName in SCIM, the provisioning loop breaks.
Conclusion
Just-in-Time provisioning transforms identity management from a reactive, batch-process model to a dynamic, event-driven architecture. By leveraging the strengths of SAML and OIDC for authentication and SCIM for standardized data exchange, organizations can ensure that user accounts are created exactly when needed, with consistent data integrity. While implementation requires careful attention to attribute mapping and trust boundaries, the result is a more resilient and maintainable identity infrastructure that eliminates the risks associated with stale synchronization scripts.
Related posts
CAS Protocol vs SAML vs OIDC: Legacy SSO Protocol Comparison
A technical comparison of CAS protocol, SAML, and OIDC for legacy authentication systems in university environments.
OIDC Claims: The Mechanics of Identity Customization
Learn how to customize user identity information in OpenID Connect by configuring OIDC claims, claim mapping, and Keycloak protocol mappers.
Social Login with OIDC: Google, GitHub & Microsoft
An examination of implementing social login using OpenID Connect with Google, GitHub, and Microsoft via Keycloak for federated identity management.