Error when providing custom Identity Provider Factory

I’m trying to provide my own custom SocialidentityProviderFactory that points to GSuite instead of Google.

The problem I’m having though is on start up I’m receiving this error:

com.mycompany.keycloak.useridmapper.GSuiteIdentityProviderFactory Unable to get public no-arg constructor   

Full stacktrace:

Unable to get public no-arg constructor             
  at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:581)
  at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:672)
  at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1232)
  at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1264)
  at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1299)
  at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1384)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.provider.DefaultProviderLoader.load(DefaultProviderLoader.java:60)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.provider.ProviderManager.load(ProviderManager.java:95)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.services.DefaultKeycloakSessionFactory.loadFactories(DefaultKeycloakSessionFactory.java:256)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.services.DefaultKeycloakSessionFactory.init(DefaultKeycloakSessionFactory.java:96)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.services.resources.KeycloakApplication.createSessionFactory(KeycloakApplication.java:260)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.services.resources.KeycloakApplication.startup(KeycloakApplication.java:125)
  at org.keycloak.keycloak-wildfly-extensions@12.0.1//org.keycloak.provider.wildfly.WildflyPlatform.onStartup(WildflyPlatform.java:29)
  at org.keycloak.keycloak-services@12.0.1//org.keycloak.services.resources.KeycloakApplication.<init>(KeycloakApplication.java:115)
  at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.core.ConstructorInjectorImpl.construct(ConstructorInjectorImpl.java:152)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.spi.ResteasyProviderFactory.createProviderInstance(ResteasyProviderFactory.java:2815)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.spi.ResteasyDeployment.createApplication(ResteasyDeployment.java:371)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:283)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:93)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:140)
  at org.jboss.resteasy.resteasy-jaxrs@3.13.2.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:42)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:117)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.security.RunAsLifecycleInterceptor.init(RunAsLifecycleInterceptor.java:78)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:103)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.ManagedServlet$DefaultInstanceStrategy.start(ManagedServlet.java:305)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.ManagedServlet.createServlet(ManagedServlet.java:145)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:588)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:559)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
  at io.undertow.servlet@2.2.2.Final//io.undertow.servlet.core.DeploymentManagerImpl.start(DeploymentManagerImpl.java:601)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:97)
  at org.wildfly.extension.undertow@21.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:78)
  at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
  at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
  at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
  at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
  at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
  at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
  at java.base/java.lang.Thread.run(Thread.java:834)
  at org.jboss.threads@2.4.0.Final//org.jboss.threads.JBossThread.run(JBossThread.java:513)
 Caused by: java.lang.NoClassDefFoundError: org/keycloak/social/google/GoogleIdentityProviderConfig
  at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
  at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3137)
  at java.base/java.lang.Class.getConstructor0(Class.java:3342)
  at java.base/java.lang.Class.getConstructor(Class.java:2151)
  at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:659)
  at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:656)
  at java.base/java.security.AccessController.doPrivileged(Native Method)
  at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:667)
  ... 48 more
 Caused by: java.lang.ClassNotFoundException: org.keycloak.social.google.GoogleIdentityProviderConfig from [Module "deployment.mycompany-spi-ear.ear.mycompany-spi-ejb.jar" from Service Module Loader]
  at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
  at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
  at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
  at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
  ... 56 more

The interesting bit seeming to originate from GoogleIdentityproviderConfig.

I have the META-INF file setup as such:
resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory

com.mycompany.keycloak.useridmapper.GSuiteIdentityProviderFactory

GSuiteIdentityProviderFactory.java

package com.mycompany.keycloak.useridmapper;

import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.social.google.GoogleIdentityProviderConfig;

public final class GSuiteIdentityProviderFactory extends AbstractIdentityProviderFactory<GSuiteIdentityProvider>
    implements SocialIdentityProviderFactory<GSuiteIdentityProvider> {
  public static final String PROVIDER_ID = "gsuite";

  @Override
  public String getName() {
    return "GSuite";
  }

  @Override
  public GSuiteIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
    return new GSuiteIdentityProvider(session, new GoogleIdentityProviderConfig(model));
  }

  @Override
  public GoogleIdentityProviderConfig createConfig() {
    return new GoogleIdentityProviderConfig();
  }

  @Override
  public String getId() {
    return PROVIDER_ID;
  }
}

I’m providing my own GSuiteIdentityProvider.java that simply extends from the GoogleIdentityProvider as I only want to augment the identity function.

package com.mycompany.keycloak.useridmapper;

import java.io.IOException;

import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.social.google.GoogleIdentityProvider;
import org.keycloak.social.google.GoogleIdentityProviderConfig;

public class GSuiteIdentityProvider extends GoogleIdentityProvider {

  public GSuiteIdentityProvider(KeycloakSession session, GoogleIdentityProviderConfig config) {
    super(session, config);
  }

  @Override
  protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenResponse, String accessToken,
      JsonWebToken idToken) throws IOException {
    BrokeredIdentityContext context = super.extractIdentity(tokenResponse, accessToken, idToken);
    context.setFirstName("test-first-name");
    context.setUserAttribute("foo", "bar");
    return context;
  }
}

Curious what I’m doing wrong here?
Longterm I wish to overwrite the doGetFederatedIdentity function in the provider similar to other social identity providers so I can make a call to the GSuite admin API to get the data I need. However, I’ve been unable to test anything due to the java errors when loading the SPI.

Note: I’m loading this as an EAR into a docker container where I drop it directly into the deployments directory. This works fine for my other plugins, but is failing specifically for this interface.

I figured out the issue. It had to do with the way I was configuring the jboss-deployment-structure.xml
I was declaring unneeded dependencies which was causing errors, I was also providing an override for the earSourceDirectory which was affecting the folder directory of the EAR output.

So in short, there were two things misconfigured that were throwing me off. Took me a long time to figure this out, but I think I can safely move forward now.