FCM in Quarkus

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"?>


4.0.0
org.keycloak.pushNotification
Push-Notification
1.0-SNAPSHOT
Push Notification

<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>




io.quarkus
quarkus-bom
${quarkus.version}
pom
import


io.quarkus.platform
quarkus-google-cloud-services-bom
3.8.2
pom
import


io.quarkus
quarkus-universe-bom
${quarkus.version}
pom
import





org.keycloak
keycloak-core
24.0.1


org.keycloak
keycloak-server-spi-private
24.0.1


org.keycloak
keycloak-server-spi
24.0.1


org.keycloak
keycloak-services
24.0.1


org.infinispan
infinispan-core
15.0.0.Final


org.keycloak
keycloak-model-infinispan
24.0.1


io.quarkus
quarkus-arc


io.quarkiverse.googlecloudservices
quarkus-google-cloud-firebase-admin
2.7.0


com.google.firebase
firebase-admin
${firebase-admin-sdk.version}


com.google.auth
google-auth-library-credentials
1.23.0





io.quarkus
quarkus-maven-plugin
${quarkus.version}



build







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.

1 Like

Hi @Bianca12334 ,

Welcome to Google Cloud Community!

Based on this GitHub issue, Firebase Cloud Messaging is not yet fully supported. You may either file a feature request or through the GitHub link I provided above.

You may also check these other documentations that you may find helpful and may act as an alternative for your current project:

Hope this helps.