Just-in-time Provisioning

Just-In-Time (JIT) provisioning in the context of Security Assertion Markup Language (SAML) refers to the process of creating a user account in an application or system at the moment of user authentication if the account does not already exist. Instead of pre-provisioning user accounts in every service or application, the service can automatically create an account based on the information provided in the SAML assertion when the user logs in for the first time. This can simplify the onboarding process for new users and reduce administrative overhead.

In the context of SAML, JIT provisioning generally works as follows:

  • Initial login: A user tries to access a service provider (SP), but they don't have an account there.
  • SAML authentication: The service provider redirects the user to the identity provider (IdP) to authenticate. The user logs in to the IdP.
  • SAML assertion: Upon successful authentication, the IdP sends a SAML assertion back to the service provider. This assertion contains the user's attributes, like their name, email, roles, or any other necessary information.
  • Account creation: The service provider checks if there's an existing account for the user. If not, it uses the information from the SAML assertion to automatically create a new user account.
  • Access granted: The user gains access to the service, either using their pre-existing account or the newly created one.

Among other benefits, JIT provisioning includes the following:

  • Reduced administrative overhead: No need to manually create accounts for each user in advance.
  • Seamless user experience: New users can get immediate access to services without waiting for accounts to be set up.
  • Reduced orphaned accounts: Since accounts are created when needed, there's a lower chance of having unused accounts that can be a security risk.

However, it's worth noting that JIT provisioning can also introduce several challenges, such as:

  • Attribute mapping: Ensuring that attributes provided by the IdP match what's expected by the SP.
  • Deprovisioning: While JIT handles automatic account creation, it doesn't address the removal of accounts when users leave an organization or change roles.
  • Role management: The SAML assertion should provide enough information to assign the correct roles or permissions, which might require coordination between the SP and IdP.

When implementing JIT provisioning with SAML, it's essential to plan out the provisioning process, handle potential edge cases, and ensure that the security implications of automatic account creation are fully understood and addressed.

JIT provisioning can be configured by inserting a respective Groovy script in the service provider editing window, as indicated below.

JIT Groovy

Here, an Azure SSO was taken as an example. The respective Groovy example for Azure is given below.

import org.apache.commons.lang.StringUtils
import org.openiam.base.response.list.GroupListResponse
import org.openiam.base.response.list.RoleListResponse
import org.openiam.base.ws.MatchType
import org.openiam.base.ws.SearchParam
import org.openiam.idm.searchbeans.GroupSearchBean
import org.openiam.idm.searchbeans.RoleSearchBean
import org.openiam.idm.srvc.role.dto.Role
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.srvc.am.GroupDataWebService
import org.openiam.srvc.am.RoleDataWebService
import org.springframework.context.ApplicationContext
import javax.annotation.Resource
import java.util.List;
import org.openiam.idm.srvc.meta.dto.MetadataType;
import org.openiam.idm.srvc.user.dto.User;
import org.openiam.ui.groovy.saml.AbstractJustInTimeSAMLAuthenticator;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.xml.schema.XSString;
import org.opensaml.xml.schema.XSInteger;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.opensaml.xml.schema.impl.XSAnyImpl;
class TestJustInTimeSAMLAuthenticator extends AbstractJustInTimeSAMLAuthenticator {
final private String userTypeId = "DEFAULT_USER"; // user type
final private String accessRoleId = ""; // role id to link user with auth provider
final private String managedSysId = ""; // managed sys id of your groups in OpenIAM
@Resource(name = "roleServiceClient")
protected RoleDataWebService roleServiceClient;
@Resource(name = "groupServiceClient")
protected GroupDataWebService groupServiceClient;
@Override
protected MetadataType getEmailType(final List<MetadataType> types) {
return types.get(0);
}
@Override
protected String getFirstName() {
if (CollectionUtils.isNotEmpty(response.getAssertions()) && CollectionUtils.isNotEmpty(response.getAssertions().get(0).getAttributeStatements())) {
for (final Attribute attribute : response.getAssertions().get(0).getAttributeStatements().get(0).getAttributes()) {
if ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname".equalsIgnoreCase(attribute.getName()))
return ((XSAnyImpl) attribute.getAttributeValues().get(0)).getTextContent();
}
}
return RandomStringUtils.randomAlphanumeric(5);
}
@Override
protected String getLastName() {
if (CollectionUtils.isNotEmpty(response.getAssertions()) && CollectionUtils.isNotEmpty(response.getAssertions().get(0).getAttributeStatements())) {
for (final Attribute attribute : response.getAssertions().get(0).getAttributeStatements().get(0).getAttributes()) {
if ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname".equalsIgnoreCase(attribute.getName()))
return ((XSAnyImpl) attribute.getAttributeValues().get(0)).getTextContent();
}
}
return RandomStringUtils.randomAlphanumeric(5);
}
@Override
protected String getEmail() {
if (CollectionUtils.isNotEmpty(response.getAssertions()) && CollectionUtils.isNotEmpty(response.getAssertions().get(0).getAttributeStatements())) {
for (final Attribute attribute : response.getAssertions().get(0).getAttributeStatements().get(0).getAttributes()) {
if ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress".equalsIgnoreCase(attribute.getName()))
return ((XSAnyImpl) attribute.getAttributeValues().get(0)).getTextContent();
}
}
return null;
}
protected void populate(final User user) {
user.setStatus(UserStatusEnum.ACTIVE);
user.mdTypeId = userTypeId;
user.addRole(accessRoleId);
final List<String> groupIds = this.getGroupsFromResponse();
if (CollectionUtils.isNotEmpty(groupIds)) {
groupIds.forEach {user.addGroup(this.getGroupIdByName(it, managedSysId))}
}
}
private String getGroupIdByName(String name, String mngSysId) {
if (StringUtils.isNotBlank(name)) {
final GroupSearchBean groupSearchBean = new GroupSearchBean();
groupSearchBean.setNameToken(new SearchParam(name, MatchType.EXACT));
groupSearchBean.setManagedSysId(mngSysId)
final GroupListResponse response = groupServiceClient.findBeans(groupSearchBean, null, 0, 1);
if (response != null && CollectionUtils.isNotEmpty(response.getList())) {
return response.getList().get(0).getId();
}
}
return null;
}
private List<String> getGroupsFromResponse() {
final List<String> roleNames = new ArrayList<>();
if (CollectionUtils.isNotEmpty(response.getAssertions()) && CollectionUtils.isNotEmpty(response.getAssertions().get(0).getAttributeStatements())) {
for (final Attribute attribute : response.getAssertions().get(0).getAttributeStatements().get(0).getAttributes()) {
if ("attr_here".equalsIgnoreCase(attribute.getName())) {
attribute.getAttributeValues().forEach {
roleNames.add(((XSAnyImpl) it).getTextContent());
}
}
}
}
return roleNames;
}
private String getRoleIdByName(String name, String mngSysId) {
if (StringUtils.isNotBlank(name)) {
final RoleSearchBean roleSearchBean = new RoleSearchBean();
roleSearchBean.setNameToken(new SearchParam(name, MatchType.EXACT));
roleSearchBean.setManagedSysId(mngSysId);
final RoleListResponse response = roleServiceClient.findBeans(roleSearchBean, null, 0, 1);
if (response != null && CollectionUtils.isNotEmpty(response.getList())) {
return response.getList().get(0).getId();
}
}
return null;
}
private String getRoleFromResponse() {
if (CollectionUtils.isNotEmpty(response.getAssertions()) && CollectionUtils.isNotEmpty(response.getAssertions().get(0).getAttributeStatements())) {
for (final Attribute attribute : response.getAssertions().get(0).getAttributeStatements().get(0).getAttributes()) {
if ("attr_here".equalsIgnoreCase(attribute.getName()))
return ((XSAnyImpl) attribute.getAttributeValues().get(0)).getTextContent();
}
}
return null;
}
}