javaspringspring-bootfirebasewhatsapp-cloud-api

Invalid JWT Signature In Firebase Java SDK ( Spring Boot )


Error getting access token for service account: 400 Bad Request
{"error":"invalid_grant","error_description":"Invalid JWT Signature."}

I am getting this error when I am trying to store my message from whatsapp cloud api in spring boot.

According to the logs, firebase has been initialized properly, and the JSON formatting as well as the key setup seems right.

This error is only happening in production, and not in my local environment, which is a confusing behaviour.

I have deployed my application on Render.com on free tier using DockerFile, which is also pretty solid as per my understanding.

Let me show you the logs,

==> Deploying...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _ | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.5.3)
2025-06-29T14:42:00.698Z  INFO 1 --- [chatbot] [           main] com.jaruratcare.ChatbotApplication       : Starting ChatbotApplication v0.0.1-SNAPSHOT using Java 17.0.15 with PID 1 (/app/app.jar started by root in /app)
2025-06-29T14:42:00.793Z  INFO 1 --- [chatbot] [           main] com.jaruratcare.ChatbotApplication       : No active profile set, falling back to 1 default profile: "default"
2025-06-29T14:42:17.996Z  INFO 1 --- [chatbot] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-06-29T14:42:18.494Z  INFO 1 --- [chatbot] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-29T14:42:18.495Z  INFO 1 --- [chatbot] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.42]
2025-06-29T14:42:20.592Z  INFO 1 --- [chatbot] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-29T14:42:20.593Z  INFO 1 --- [chatbot] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 18895 ms
Base64: ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiamFydXJhdGNhcmUtZGIiLAogICJwcml2YXRlX2tleV9pZCI6ICIxNTY1MWJiZWNmNzk3ZTRkY2M5ZGY4NGI3YjA5ODE5OTIxZGU1M2UxIiwKICAicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdkFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLWXdnZ1NpQWdFQUFvSUJBUUNpZERWb0pycVNvY1RiXG40c3VacEF2bmcwdUdkOFI3ZWJBZWx3TE9IU1lmdU5vd2ZRQ3c2WnF3dnQ5ZVR2cXJzc0dGN1ZKTmprNkNKcWp4XG5OdXFmMGJOZk5hbHE1MlJhUnpJZXcxV0Mvb0VuWHJuaUhwNEwraTczUTd5S04rZWFRT0VxZHZSTEZ2WWpTb1E5XG5oMEpQVEVnaHdUZ2IvU05rWW1HMVc3dXFZdDY5ME9jRkJEWHZKR2hKSUtxaFdNV0hiekpiVVhaLy9WYnRWM0EzXG5MemZQUFd2UUh1ZU5zb1RsYU0wT3FRVzdLVUoyaGhhclBwQkZxUUwyNllQN3YrUlZVOXQ4dFBMOEw2cnFoaDgxXG5EMzJsUjY4U0w1OTVxSGkvK0R0TU1GK3FUM3NCNmpuS3RQcVVDTHo3YTBxWGk4ZmVVQ3pvaXRjTU91Njg4ZStQXG5XNElIQVBiekFnTUJBQUVDZ2dFQUdYbUpxblJ3UzNBc3M2S0Q5T3JocHdOU3c1cGlhRTV6ek1nM2UyVGN4cHF4XG5OR3RWd1doUWhxalgxQzBrWitwWEtJNUhtcnZYR2RmQUpGdE5QWlNmd1Mxa0RNeU9VdzNaRVRQbVkrMVBDWUowXG5PYjQ5WUhseUJmcE1SWXh3dUNLMmJIZE01YjJMQ1l1MDR3VWo0OVY5ZGpXYmUrQWRQYStqek5HK3d3QlFGNG03XG5hQWJNZ0JSZXJVaHozNWVHdnF3UUlyMVB3a0ovb1M2RWdPK3Nyd0RCQUZEWXFmMzdpRGVFRmNublJYQlB3Z3pJXG5sK1daSFNOYUI2MVg5RGNkVTQ3NFpLTytUZmpsOTBxWjhRc3l3bW9IZjgyT2RFQ0owdUswU1YzRFhXZENRTllaXG5kMFI3bUFMRnNiazlmSlNHR0gya05MR0RHRnBvbWxQanR5OSs0NnZUdVFLQmdRRGpuU252UWtEemVhenVzRnVyXG5STS9aa3IzeFJEb1VCU0pleFNwKy9CTWJNVFJnbm1NVkR2UmR1N2VieTB3Njh6dXZJTDZGTzl6cFgrYjFoM3RvXG5jb3d4TDRRZUdRb09XSytsWTJsMk9HR1RKN2E5OTcyZHE1djFiSnIzUENvVFQ0UFhZMzlEOXdRWndDQit6T2UwXG5aVHVyc05YNlBrTWJoeXJmT0dkOWt4WVNoUUtCZ1FDMnRyekxYN2gzdThVY2Qyci9RTHZzWVZBdEl6SHdjQ013XG55N2NESTdBN0h5Z1VodmljeTVmR1RWYXI0OTMrOWJCUjg3Y1lERUJ6U2tERUVrdDRXMVpBVWJpUlNlVkZSOFFwXG5STlF4THliNkVVRnpJdFlOMWVYZkY4M2l0WktMVnpoM3ZldzdlSkRRUWF1M01nckRYTEJ5NlRKOUowN0RFekMwXG52bVl0a3pZcEZ3S0JnQjVycCtvaFNic0F2UnNkbVBMWC9JdVFzREFwdFRWdHhqMHlUczZ2OUFIRFhzTzNCM2tPXG5DWnpwZE0ydXF4NkoyYVU3VkJ0TWQrYjgrVXBCZEQzdWFVdzlsSVBOUnVuSjhwZkJKSnczUnRzN0NKSjFKUE9JXG50d3hqR29jR2xIMEdPSkcxNjVGeTF5cDE0aUh3VXR0cVdFSkhIU0lYNWpJc3E0OHY0NjBGMW9KZEFvR0FXSUdZXG5YbU9WdGRvdEZ5YjVtMysrQ0Rqelg0b0c3Tk5GR0tQMW1QMjJwa3puVzFvdUg3VzNaWVdkRUZLbS9KUUN2dy94XG5oeUtZTGNDd01zb3g2TEZGekk1SmU0ZHlHZWN6ckZIOC8zNDV3VFNPb2Jra096dDd5Rjk5aWU0dWVIVGRrNU5hXG5WVXlIeVhtbU5tMTFMVUUrNzhycWZCbUlXNUoySzNkM2cyZ2wraTBDZ1lCQmRwMURrenZaMWZHb2VMSi8vZjNlXG5oVStuRjVhZGViQ2lrNk1UUFR0QURtOUgyTm9nNXlLZkVqVElXUVdpbU9vcGdxcFZCUktoNlpMcGJRZkxMeFkyXG5rTTUwUTVXaGhkRHRIMDAxU1FMRVRucWZrZE9sblh2UzMxSkpSTm9KMTVHSHRmM1FXMHFKL2hlNnhwSDBILy84XG5TOCtqSDlrRTY3OGlZTy9ZRjhiZ01RPT1cbi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS1cbiIsCiAgImNsaWVudF9lbWFpbCI6ICJmaXJlYmFzZS1hZG1pbnNkay1mYnN2Y0BqYXJ1cmF0Y2FyZS1kYi5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsCiAgImNsaWVudF9pZCI6ICIxMTQzMTIxNDMzMTA4NzA4MzI2NjAiLAogICJhdXRoX3VyaSI6ICJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvYXV0aCIsCiAgInRva2VuX3VyaSI6ICJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ZpcmViYXNlLWFkbWluc2RrLWZic3ZjJTQwamFydXJhdGNhcmUtZGIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAogICJ1bml2ZXJzZV9kb21haW4iOiAiZ29vZ2xlYXBpcy5jb20iCn0K
Decoded bytes [B@14998e21
GoogleCredentials done
Firebase options done
Firebase initialized
2025-06-29T14:42:33.191Z  INFO 1 --- [chatbot] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-06-29T14:42:33.692Z  INFO 1 --- [chatbot] [           main] com.jaruratcare.ChatbotApplication       : Started ChatbotApplication in 41.3 seconds (process running for 51.264)
2025-06-29T14:42:34.390Z  INFO 1 --- [chatbot] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-29T14:42:34.391Z  INFO 1 --- [chatbot] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-06-29T14:42:34.392Z  INFO 1 --- [chatbot] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
==> Your service is live šŸŽ‰
==> 
==> ///////////////////////////////////////////////////////////
==> 
==> Available at your primary URL https://jaruratcare-bot.onrender.com
==> 
==> ///////////////////////////////////////////////////////////
šŸ“„ Message received!
šŸ“ž From: 919149469833
šŸ’¬ Text: hello assistant how are you?
🧠 Cloudflare raw response: {result={response= Hello! I'm an assistant from Jarurat Care Foundation, here to offer support and helpful answers for those affected by cancer. I'm here to make things easier for you. How can I assist you today? (Max 1000 characters), usage={prompt_tokens=57, completion_tokens=55, total_tokens=112}}, success=true, errors=[], messages=[]}
šŸ“¤ WhatsApp send status: 200
šŸ“ Attempting to save message to Firebase
šŸ“ž From: 919149469833
šŸ’¬ Text: hello assistant how are you?
Getting firestore client
Apps: [FirebaseApp{name=[DEFAULT]}]
Is default app initialized? [DEFAULT]
šŸ“‚ Firebase doc reference created: 7zjBZsb3AJPw5BnPySwm
āŒ Firebase write failed
java.util.concurrent.ExecutionException: com.google.api.gax.rpc.UnavailableException: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to obtain metadata
    at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:588)
    at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:567)
    at com.google.common.util.concurrent.FluentFuture$TrustedFuture.get(FluentFuture.java:91)
    at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture.java:66)
    at com.jaruratcare.service.FirebaseService.save(FirebaseService.java:44)
    at com.jaruratcare.controller.WhatsappController.processMessage(WhatsappController.java:73)
    at com.jaruratcare.controller.WhatsappController.lambda$receiveMessage$0(WhatsappController.java:51)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
    at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: com.google.api.gax.rpc.UnavailableException: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to obtain metadata
    at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:112)
    at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:41)
    at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:86)
    at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:66)
    at com.google.api.gax.grpc.GrpcExceptionCallable$ExceptionTransformingFuture.onFailure(GrpcExceptionCallable.java:97)
    at com.google.api.core.ApiFutures$1.onFailure(ApiFutures.java:84)
    at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1132)
    at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
    at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1270)
    at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:1038)
    at com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:808)
    at io.grpc.stub.ClientCalls$GrpcFuture.setException(ClientCalls.java:574)
    at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:544)
    at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
    at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
    at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
    at com.google.api.gax.grpc.ChannelPool$ReleasingClientCall$1.onClose(ChannelPool.java:541)
    at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:567)
    at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:71)
    at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:735)
    at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:716)
    at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
    at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    ... 1 more
Caused by: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to obtain metadata
    at io.grpc.Status.asRuntimeException(Status.java:539)
    ... 14 more
Caused by: com.google.auth.oauth2.GoogleAuthException: Error getting access token for service account: 400 Bad Request
POST https://oauth2.googleapis.com/token
{"error":"invalid_grant","error_description":"Invalid JWT Signature."}, iss: firebase-adminsdk-fbsvc@jaruratcare-db.iam.gserviceaccount.com
    at com.google.auth.oauth2.GoogleAuthException.createWithTokenEndpointResponseException(GoogleAuthException.java:129)
    at com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:541)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:269)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:266)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at com.google.auth.oauth2.OAuth2Credentials$RefreshTask.run(OAuth2Credentials.java:633)
    ... 3 more
Caused by: com.google.api.client.http.HttpResponseException: 400 Bad Request
POST https://oauth2.googleapis.com/token
{"error":"invalid_grant","error_description":"Invalid JWT Signature."}
    at com.google.api.client.http.HttpResponseException$Builder.build(HttpResponseException.java:293)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1118)
    at com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:538)
    ... 7 more

Let me show you the Firebase related code,


@Component
public class FirebaseInitializer {

    @PostConstruct
    public void initialize() {
        try {

            String base64Creds = System.getenv("FIREBASE_CREDENTIALS_BASE64");
            System.out.println("Base64: " + base64Creds );

            String decodedJson = new String(Base64.getDecoder().decode(base64Creds), StandardCharsets.UTF_8);

            decodedJson = decodedJson.replace("\\n", "\n");
            System.out.println("Decoded bytes after replacing" + decodedJson );

            InputStream stream = new ByteArrayInputStream(decodedJson.getBytes(StandardCharsets.UTF_8));

            GoogleCredentials credentials = GoogleCredentials
                    .fromStream(stream);
            System.out.println("GoogleCredentials done" );

            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(credentials)
                    .setProjectId("jaruratcare-db")
                    .build();
            System.out.println("Firebase options done" );

            FirebaseApp.initializeApp(options);
            System.out.println("Firebase initialized" );

        } catch (Exception e) {
            System.out.println("āŒ Failed to initialize Firebase");
            e.printStackTrace();
        }
    }
}
@Service
public class FirebaseService {

    public void save(String from, String text){
        try {
            System.out.println("šŸ“ Attempting to save message to Firebase");
            System.out.println("šŸ“ž From: " + from);
            System.out.println("šŸ’¬ Text: " + text);

            System.out.println("Getting firestore client" );
            System.out.println("Apps: " + FirebaseApp.getApps());
            System.out.println("Is default app initialized? " + FirebaseApp.getInstance().getName());
            Firestore db = FirestoreClient.getFirestore();

            if (db == null) {
                System.out.println("āŒ Firestore DB is null! Firebase may not be initialized.");
                return;
            }

            if (FirebaseApp.getApps().isEmpty()) {
                System.out.println("🚫 FirebaseApp not initialized. Skipping save.");
                return;
            }

            Map<String, Object> data = new HashMap<>();
            data.put("number", from);
            data.put("message", text);
            data.put("timestamp", System.currentTimeMillis());

            DocumentReference ref = db.collection("messages").document();
            System.out.println("šŸ“‚ Firebase doc reference created: " + ref.getId());

            ref.set(data).get();
            System.out.println("āœ… Message saved to Firebase (confirmed)");
        } catch (Exception e) {
            System.out.println("āŒ Firebase write failed");
            e.printStackTrace();
        }
    }



}


Solution

  • I figured out the issue. It was because google cloud was disabling the api key, just because I was exposing the firebase json file by keeping it in the /resources folder of my spring boot project. I thought that'd be fine as it was currently in development phase. But google's rules are strict. It didn't put an effort to notify the developer about api getting disabled as soon as the project gets pushed to github.

    It was solved by deleting the firebase json file from the project. Using environment variables to put the newly generated firebase key to where it was deployed (in my case, it was Render.com), and using base64 encoding as the value in environment variable so that while decoding, no whitespace issues or mismatch issues occur.

    And it worked, provided that the key no longer is exposed to github or anywhere on internet !