Extending FreeMarkerLoginFormsProvider for custom theme attributes

Hello

I’m currently exploring the upgrade from major 16 to 17 and trying to figure out if it will work with our custom services.

After some finagling, i’ve managed to get pretty much everything to work except the following feature which we really need.

I have a custom SPI that extends FreeMarkLoginFormsProvider, i’m extending it in order to add attributes to the createCommonAttributes method, which then let’s me add new attributes to the theme, the attributes are coming form the realm attributes.
(Everything would be much simpler if the ftl files had access to realm attributes, but they dont)

But the problem is that in order to extend this class, i needed to add a custom spi in the standalone file as such:

embed-server --server-config=standalone-ha.xml
/subsystem=keycloak-server/spi=login:add()
/subsystem=keycloak-server/spi=login/provider=freemarker:add(enabled="false")
/subsystem=keycloak-server/spi=login/provider=csps-freemarker-login-messages:add(enabled="true")
stop-embedded-server

The above is not possible anymore in keycloak 17 and up since jboss-cli.sh is no longer in the bin folder of keycloak.

I’m wondering, is extending the theme and adding new attributes that can be fetched from the ftl files is still possible through the FreeMarkerLoginFormsProvider class in keycloak 17 and up?

Here’s my class for the interested

public class CustomFreeMarkerLoginFormsProvider extends FreeMarkerLoginFormsProvider {

    public CustomFreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
        super(session, freeMarker);
    }

    @Override
    protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle,
            UriBuilder baseUriBuilder, LoginFormsPages page) {
        super.createCommonAttributes(theme, locale, messagesBundle, baseUriBuilder, page);

        if (realm != null) {
            // Setting the messages and title from the realm attributes.
            if ("fr".equals(locale.toString().toLowerCase())) {
                attributes.put("dangerMessage", realm.getAttribute("dangerMessageFrench"));
                attributes.put("warningMessage", realm.getAttribute("warningMessageFrench"));
                attributes.put("infoMessage", realm.getAttribute("infoMessageFrench"));
                attributes.put("dangerMessageTitle", realm.getAttribute("dangerMessageTitleFrench"));
                attributes.put("warningMessageTitle", realm.getAttribute("warningMessageTitleFrench"));
                attributes.put("infoMessageTitle", realm.getAttribute("infoMessageTitleFrench"));
            } else {
                attributes.put("dangerMessage", realm.getAttribute("dangerMessageEnglish"));
                attributes.put("warningMessage", realm.getAttribute("warningMessageEnglish"));
                attributes.put("infoMessage", realm.getAttribute("infoMessageEnglish"));
                attributes.put("dangerMessageTitle", realm.getAttribute("dangerMessageTitleEnglish"));
                attributes.put("warningMessageTitle", realm.getAttribute("warningMessageTitleEnglish"));
                attributes.put("infoMessageTitle", realm.getAttribute("infoMessageTitleEnglish"));
            }

            // Getting the CSPS maintenance mode execution config.
            AuthenticatorConfigModel authenticatorConfig = realm
                    .getAuthenticatorConfigByAlias("CSPS maintenance mode execution config");

            if (authenticatorConfig != null) {
                Boolean maintenance = Boolean.parseBoolean(authenticatorConfig.getConfig().get("maintenance"));
                if (maintenance) {
                    // The maintenance config is enabled, setting a theme attribute.
                    attributes.put("maintenanceModeEnabled", maintenance);
                }
            }
        }
    }
}

As you can see, i’m setting up attributes that are fetched from the realm attributes. Then my ftl files can access the attributes this way:

<@dangerMessage?interpret />

I’ve found this

I’ll give it a try and post some updates

The above seems to have done the trick.

I’ve modified my init script to start keycloak with the following 2 attributes:

/opt/keycloak/bin/kc.sh "${keycloak_cmd_arguments[@]}" --spi-login-freemarker-enabled=false --spi-login-csps-freemarker-login-messages-enabled=true

This starts keycloak with the SPI being updated.
The theme can now access the variables.

I’m trying to migrate from keycloak 20.0.2 to keycloak 25 and this parameter doesn’t work anymore

--spi-login-provider=freemarkerExt

I see this error when I try to open login page

ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NullPointerException: Cannot invoke "org.keycloak.forms.login.LoginFormsProvider.setAuthenticationSession(org.keycloak.sessions.AuthenticationSessionModel)" because the return value of "org.keycloak.models.KeycloakSession.getProvider(java.lang.Class)" is null
        at org.keycloak.protocol.AuthorizationEndpointBase.createAuthenticationSession(AuthorizationEndpointBase.java:214)

These parameters don’t help, the same error

--spi-login-freemarker-enabled=false --spi-login-freemarkerExt-enabled=true
1 Like

I am having the same issue! If you have found a workaround please share it !

The following logic is used to determine the default provider:

  1. The explicitly configured default provider
  2. The provider with the highest order (providers with order ⇐ 0 are ignored)
  3. The provider with the id set to default

so you need to override getOrder method in your factory.

    @Override
    public int order() {
        return 1;
    }
2 Likes

See my reply to Inject dynamic variable from backend java to freemarker template - #3 by michael.carter.kbr