When using the java admin API I find it’s easiest to perform the operation using the screens with chrome developer tools open so you can see what actually gets posted to the back end in the network tab. As far as I know the front end uses the java admin API to communicate with the back end.
Here’s the code I use to add a SAML IdP via the java admin API. The first part adds the IdP. The second part adds three attribute mappings for email, firstName, and lastName. The third part configures the realm to always use this IdP thus bypassing the normal keycloak login screen. You can see that the code actually assumes this is a google workspace SAML IdP as far as the attribute mappings go but you can easily modify that to suit your own needs.
public void addSamlIdentityProvider(String realm, String name, String entityId, String idpEntityId, String signingCertificates, String singleSignOnServiceUrl,
boolean wantAssertionsSigned, boolean validateSignature) {
IdentityProviderRepresentation identityProviderRepresentation = new IdentityProviderRepresentation();
identityProviderRepresentation.setAlias(name);
identityProviderRepresentation.setDisplayName("");
identityProviderRepresentation.setProviderId("saml");
identityProviderRepresentation.setTrustEmail(true);
identityProviderRepresentation.setConfig(Map.ofEntries(
Map.entry("addExtensionsElementWithKeyInfo", "false"),
Map.entry("allowCreate", "true"),
Map.entry("allowedClockSkew", "0"),
Map.entry("attributeConsumingServiceIndex", "0"),
Map.entry("attributeConsumingServiceName", ""),
Map.entry("backchannelSupported", "false"),
Map.entry("enabledFromMetadata", "false"),
Map.entry("entityId", entityId),
Map.entry("forceAuthn", "false"),
Map.entry("guiOrder", ""),
Map.entry("idpEntityId", idpEntityId),
Map.entry("loginHint", "false"),
Map.entry("nameIDPolicyFormat", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"),
Map.entry("postBindingAuthnRequest", "true"),
Map.entry("postBindingLogout", "false"),
Map.entry("postBindingResponse", "true"),
Map.entry("principalType", "SUBJECT"),
Map.entry("sendClientIdOnLogout", "false"),
Map.entry("sendIdTokenOnLogout", "true"),
Map.entry("signSpMetadata", "false"),
Map.entry("signingCertificate", signingCertificates),
Map.entry("singleLogoutServiceUrl", ""),
Map.entry("singleSignOnServiceUrl", singleSignOnServiceUrl),
Map.entry("syncMode", "FORCE"),
Map.entry("validateSignature", validateSignature ? "true" : "false"),
Map.entry("wantAssertionsEncrypted", "false"),
Map.entry("wantAssertionsSigned", wantAssertionsSigned ? "true" : "false"),
Map.entry("wantAuthnRequestsSigned", "false")));
keycloak.realm(realm).identityProviders().create(identityProviderRepresentation);
IdentityProvidersResource identityProvidersResource = keycloak.realm(realm).identityProviders();
IdentityProviderMapperRepresentation emailIdentityProviderMapperRepresentation = new IdentityProviderMapperRepresentation();
emailIdentityProviderMapperRepresentation.setName("email");
emailIdentityProviderMapperRepresentation.setIdentityProviderAlias("google");
emailIdentityProviderMapperRepresentation.setIdentityProviderMapper("saml-user-attribute-idp-mapper");
emailIdentityProviderMapperRepresentation.setConfig(Map.of(
"attribute.friendly.name", "",
"attribute.name", "email",
"attribute.name.format", "ATTRIBUTE_FORMAT_BASIC",
"syncMode", "FORCE",
"user.attribute", "email"
));
identityProvidersResource.get("google").addMapper(emailIdentityProviderMapperRepresentation);
IdentityProviderMapperRepresentation firstNameIdentityProviderMapperRepresentation = new IdentityProviderMapperRepresentation();
firstNameIdentityProviderMapperRepresentation.setName("firstName");
firstNameIdentityProviderMapperRepresentation.setIdentityProviderAlias("google");
firstNameIdentityProviderMapperRepresentation.setIdentityProviderMapper("saml-user-attribute-idp-mapper");
firstNameIdentityProviderMapperRepresentation.setConfig(Map.of(
"attribute.friendly.name", "",
"attribute.name", "firstName",
"attribute.name.format", "ATTRIBUTE_FORMAT_BASIC",
"syncMode", "FORCE",
"user.attribute", "firstName"
));
identityProvidersResource.get("google").addMapper(firstNameIdentityProviderMapperRepresentation);
IdentityProviderMapperRepresentation lastNameIdentityProviderMapperRepresentation = new IdentityProviderMapperRepresentation();
lastNameIdentityProviderMapperRepresentation.setName("lastName");
lastNameIdentityProviderMapperRepresentation.setIdentityProviderAlias("google");
lastNameIdentityProviderMapperRepresentation.setIdentityProviderMapper("saml-user-attribute-idp-mapper");
lastNameIdentityProviderMapperRepresentation.setConfig(Map.of(
"attribute.friendly.name", "",
"attribute.name", "lastName",
"attribute.name.format", "ATTRIBUTE_FORMAT_BASIC",
"syncMode", "FORCE",
"user.attribute", "lastName"
));
identityProvidersResource.get("google").addMapper(lastNameIdentityProviderMapperRepresentation);
AuthenticationManagementResource authenticationManagementResource = keycloak.realm(realm).flows();
AuthenticationExecutionInfoRepresentation identityProviderRedirectorAuthenticationExecutionInfoRepresentation = null;
List<AuthenticationExecutionInfoRepresentation> authenticationExecutionInfoRepresentations = authenticationManagementResource.getExecutions("browser");
for(AuthenticationExecutionInfoRepresentation authenticationExecutionInfoRepresentation : authenticationExecutionInfoRepresentations) {
if(StringUtils.equals(authenticationExecutionInfoRepresentation.getProviderId(), "identity-provider-redirector")) {
identityProviderRedirectorAuthenticationExecutionInfoRepresentation = authenticationExecutionInfoRepresentation;
}
}
AuthenticationExecutionInfoRepresentation authenticationExecutionInfoRepresentation = new AuthenticationExecutionInfoRepresentation();
authenticationExecutionInfoRepresentation.setRequirement("REQUIRED");
authenticationExecutionInfoRepresentation.setId(identityProviderRedirectorAuthenticationExecutionInfoRepresentation.getId());
authenticationExecutionInfoRepresentation.setIndex(2);
authenticationExecutionInfoRepresentation.setLevel(0);
authenticationExecutionInfoRepresentation.setPriority(25);
authenticationManagementResource.updateExecutions("browser", authenticationExecutionInfoRepresentation);
AuthenticatorConfigRepresentation identityProviderRedirectorAuthenticatorConfigRepresentation = new AuthenticatorConfigRepresentation();
identityProviderRedirectorAuthenticatorConfigRepresentation.setAlias("google");
identityProviderRedirectorAuthenticatorConfigRepresentation.setConfig(Map.of("defaultProvider", "google"));
authenticationManagementResource.newExecutionConfig(identityProviderRedirectorAuthenticationExecutionInfoRepresentation.getId(), identityProviderRedirectorAuthenticatorConfigRepresentation);
}
If you’re starting up keycloak via docker compose here’s the start command I use to enable debug output which can also be helpful when diagnosing problems.
command: ["start-dev", "--spi-theme-static-max-age=-1", "--spi-theme-cache-themes=false", "--spi-theme-cache-templates=false", "--log-level=info,org.keycloak:debug,org.keycloak.transaction.JtaTransactionWrapper:info,org.keycloak.services.scheduled.ScheduledTaskRunner:info"]