FCM in Keycloak 24.0.1 for push notification module

I’m trying to integrate a push notification module in Keycloak and I’m having problems using the FCM service in quarkus. I want to use the FCM service for sending messages to mobile devices. I have the following code:


package org.keycloak.pushNotification.interfaces;

import com.google.auth.oauth2.GoogleCredentials;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

@ApplicationScoped
public class FirebaseInitializer {
    private String accessToken;

    @PostConstruct
    public void initialize() {
        try {
            InputStream serviceAccount = Thread.currentThread().getContextClassLoader().getResourceAsStream("/fcm.json");
            if (serviceAccount == null) {
                throw new FileNotFoundException("Could not find 'fcm.json' in the classpath");
            }
            GoogleCredentials googleCredentials = GoogleCredentials.fromStream(serviceAccount)
                    .createScoped(List.of("https://www.googleapis.com/auth/firebase.messaging"));
            googleCredentials.refreshIfExpired();
            accessToken = googleCredentials.getAccessToken().getTokenValue();
;
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(googleCredentials)
                    .build();

            if (FirebaseApp.getApps().isEmpty()) {
                System.out.println("S-a realizat initializarea");
                FirebaseApp.initializeApp(options);
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to initialize Firebase", e);
        }
    }

    public String getAccessToken() {
        return accessToken;
    }
}

And here I have a specific Keycloak interface where I want to send messages to users:


package org.keycloak.pushNotification.interfaces;

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.ws.rs.core.Response;
import org.keycloak.authentication.*;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.models.UserModel;
import org.keycloak.pushNotification.model.PNCredentialModel;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Random;

public class PNRequiredAction implements RequiredActionProvider, CredentialRegistrator {

    FirebaseInitializer firebaseInitializer = new FirebaseInitializer();
    public static final String PROVIDER_ID = "push_notification_config";

    @Override
    public void evaluateTriggers(RequiredActionContext context) {

    }

    @Override
    public void requiredActionChallenge(RequiredActionContext context) {
        UserModel user = context.getUser();
        List<String> tokensFCM = user.getAttributes().get("tokenFCM");
        String tokenFCM = tokensFCM.isEmpty() ? null : tokensFCM.get(0);
        String code = generateCode();

        if (tokensFCM != null) {
            try {
                firebaseInitializer.initialize();
                String accessToken = firebaseInitializer.getAccessToken();
                HttpClient client = HttpClient.newHttpClient();
                HttpRequest request = HttpRequest.newBuilder()
                        .uri(new URI("https://fcm.googleapis.com/fcm/send"))
                        .header("Content-Type", "application/json")
                        .header("Authorization", "Bearer " + accessToken)
                        .POST(HttpRequest.BodyPublishers.ofString(buildMessage(tokenFCM, "Title", "Code: " + code)))
                        .build();

                HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
                System.out.println("Access token: " + accessToken);
                System.out.println("Response status: " + response.statusCode());
                System.out.println("Response body: " + response.body());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

//        if (tokenFCM != null) {
//            Message message = Message.builder()
//                    .setToken(tokenFCM)
//                    .setNotification(Notification.builder()
//                            .setTitle("Autentificare noua")
//                            .setBody("Aici este codul tau: " + code)
//                            .build())
//                    .build();
//
//            // LOGGER HERE
//            try {
//                String response = FirebaseMessaging.getInstance().send(message);
//                System.out.println("Successfully sent message: " + response);
//            } catch (Exception e) {
//                System.err.println("Failed to send message: " + e.getMessage());
//                return;
//            }
//        }

        Response challenge = context.form()
                .createForm("push-notification-form.ftl");
        context.challenge(challenge);
    }

    private String buildMessage(String token, String title, String body) {
        JsonObject message = Json.createObjectBuilder()
                .add("to", token)
                .add("notification", Json.createObjectBuilder()
                        .add("title", title)
                        .add("body", body))
                .build();
        return message.toString();
    }

    @Override
    public void processAction(RequiredActionContext context) {
        String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("code_access"));
        PNCredentialProvider pn = (PNCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "push-notification");
        pn.createCredential(context.getRealm(), context.getUser(), PNCredentialModel.createPN());
        context.success();
    }

    @Override
    public void close() {

    }

    private String generateCode() {
        Random random = new Random();
        int code = 100000 + random.nextInt(900000);
        return String.valueOf(code);
    }
}

pom.xml:


<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.keycloak.pushNotification</groupId>
  <artifactId>Push-Notification</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>Push Notification</name>
  <properties>
    <firebase-admin-sdk.version>9.2.0</firebase-admin-sdk.version>
    <java.version>17</java.version>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <microprofile-jwt-auth-api.version>2.1</microprofile-jwt-auth-api.version>
    <quarkus.version>3.8.2</quarkus.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>io.quarkus.platform</groupId>
        <artifactId>quarkus-google-cloud-services-bom</artifactId>
        <version>3.8.2</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-universe-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-core</artifactId>
      <version>24.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-server-spi-private</artifactId>
      <version>24.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-server-spi</artifactId>
      <version>24.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-services</artifactId>
      <version>24.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.infinispan</groupId>
      <artifactId>infinispan-core</artifactId>
      <version>15.0.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-model-infinispan</artifactId>
      <version>24.0.1</version>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkiverse.googlecloudservices</groupId>
      <artifactId>quarkus-google-cloud-firebase-admin</artifactId>
      <version>2.7.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.firebase</groupId>
      <artifactId>firebase-admin</artifactId>
      <version>${firebase-admin-sdk.version}</version>
    </dependency>
    <dependency>
      <groupId>com.google.auth</groupId>
      <artifactId>google-auth-library-credentials</artifactId>
      <version>1.23.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-maven-plugin</artifactId>
        <version>${quarkus.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>build</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

And it keeps giving me this error at runtime:

2024-03-24 09:54:49,039 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NoClassDefFoundError: com/google/auth/oauth2/GoogleCredentials
at org.keycloak.pushNotification.interfaces.FirebaseInitializer.initialize(FirebaseInitializer.java:25)
at org.keycloak.pushNotification.interfaces.PNRequiredAction.requiredActionChallenge(PNRequiredAction.java:37)
at org.keycloak.services.managers.AuthenticationManager.executeAction(AuthenticationManager.java:1275)
at org.keycloak.services.managers.AuthenticationManager.lambda$executionActions$15(AuthenticationManager.java:1222)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:400)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:528)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
at org.keycloak.services.managers.AuthenticationManager.executionActions(AuthenticationManager.java:1223)
at org.keycloak.services.managers.AuthenticationManager.actionRequired(AuthenticationManager.java:1111)
at org.keycloak.services.managers.AuthenticationManager.nextActionAfterAuthentication(AuthenticationManager.java:958)
at org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1079)
at org.keycloak.services.resources.LoginActionsService.requiredActionGET(LoginActionsService.java:1061)
at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$requiredActionGET_900f1400af417d7ade6b5fdd106784903c8de34e.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.ClassNotFoundException: com.google.auth.oauth2.GoogleCredentials
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:115)
at io.quarkus.bootstrap

If someone can help me, please explain what I’m doing wrong that I can’t solve it.