Keycloak: Failed to verify identity token: Invalid token issuer

Software Engineer at BMW, Oracle Certified Professional Java SE 11 Developer
Two whole days of my life, down the drain! Thankfully, it was just my pet project, but I ended up sacrificing an entire weekend wrestling with this enigma. So, I'm writing this blog post to save you from the same fate.
Problem:
I am using Quarkus and keycloak to protect my routes. With keycloak's start-dev everything was working perfectly fine, but as soon as I tried to deploy everything with docker in production, I got following error as soon as I issued an access token and tried to access my protected route with it.
keycloak | 2023-10-30 18:54:42,832 WARN [org.keycloak.events] (executor-thread-13)
type=PERMISSION_TOKEN_ERROR, realmId=36382947-8ffa-4a8a-9881-2b7eeb84f800,
clientId=backend-service, userId=null, ipAddress=192.168.16.4,
error=invalid_token, reason='HTTP 500 Internal Server Error',
auth_method=oauth_credentials, audience=backend-service,
grant_type=urn:ietf:params:oauth:grant-type:uma-ticket,
permission=df1b74a9-3f10-499d-a581-368de48e512b,
client_auth_method=client-secret
"Fantastic! HTTP 500 Internal Server Errors, the crème de la crème of exceptions. They are even more delightful when they emanate from a charming 3rd-party package like our dear friend, Keycloak".
"Should you encounter a problem, consult the log files, my friend."
This way you'll become the Sherlock Holmes of software mysteries!
So I started my keycloak instance with --log-level=DEBUG, here the full code
keycloak:
image: quay.io/keycloak/keycloak:22.0.5
container_name: keycloak
ports:
- "8443:8443"
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak_user
KC_DB_PASSWORD: secret
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
volumes:
- ./cert/keycloak-cert.pem:/etc/keycloak-cert.pem
- ./cert/keycloak-private-key.pem:/etc/keycloak-key.pem
command: ['start',
'--hostname-strict=false',
'--https-certificate-file=/etc/keycloak-cert.pem',
'--https-certificate-key-file=/etc/keycloak-key.pem',
'--log-level=DEBUG']
depends_on:
- "postgres"
Sherlock Holmes stumbled upon his first clue because the log files gave him following hint of an Invalid token issuer.
keycloak | 2023-10-30 19:16:23,186 DEBUG [org.keycloak.services.managers.AuthenticationManager] (executor-thread-2)
Failed to verify identity token: Invalid token issuer. Expected
'https://keycloak:8443/realms/myrealm', but was
'https://localhost:8443/realms/myrealm'
Aha! After that two more exceptions decided to crash the party and I'm not one to keep secrets:
Troublemaker number one
keycloak | 2023-10-30 19:16:23,186 DEBUG [org.keycloak.authorization.authorization.AuthorizationTokenService] (executor-thread-2) PERMISSION_TOKEN_ERROR: org.keycloak.services.ErrorResponseException: HTTP 500 Internal Server Error
keycloak | at org.keycloak.authorization.common.KeycloakIdentity.<init>(KeycloakIdentity.java:73)
keycloak | at org.keycloak.authorization.common.KeycloakIdentity.<init>(KeycloakIdentity.java:68)
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.lambda$static$0(AuthorizationTokenService.java:139)
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.createEvaluationContext(AuthorizationTokenService.java:474)
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.authorize(AuthorizationTokenService.java:213)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.permissionGrant(TokenEndpoint.java:986)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequestInternal(TokenEndpoint.java:233)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:190)
keycloak | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
keycloak | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
keycloak | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
keycloak | at java.base/java.lang.reflect.Method.invoke(Method.java:568)
keycloak | at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
keycloak | at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
keycloak | at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:174)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:131)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:33)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
keycloak | at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
keycloak | at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:82)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:147)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:84)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:44)
keycloak | at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
keycloak | at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
keycloak | at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak | at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:58)
keycloak | at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:36)
keycloak | at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
keycloak | at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
keycloak | at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak | at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
keycloak | at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
keycloak | at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
keycloak | at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
keycloak | at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
keycloak | at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
keycloak | at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
keycloak | at java.base/java.lang.Thread.run(Thread.java:840)
and Troublemaker number two
keycloak | 2023-10-30 19:16:23,187 DEBUG [org.keycloak.authorization.authorization.AuthorizationTokenService] (executor-thread-2) Error while evaluating permissions: org.keycloak.services.CorsErrorResponseException: HTTP 500 Internal Server Error
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.lambda$static$0(AuthorizationTokenService.java:142)
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.createEvaluationContext(AuthorizationTokenService.java:474)
keycloak | at org.keycloak.authorization.authorization.AuthorizationTokenService.authorize(AuthorizationTokenService.java:213)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.permissionGrant(TokenEndpoint.java:986)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequestInternal(TokenEndpoint.java:233)
keycloak | at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:190)
keycloak | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
keycloak | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
keycloak | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
keycloak | at java.base/java.lang.reflect.Method.invoke(Method.java:568)
keycloak | at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
keycloak | at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
keycloak | at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
keycloak | at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:174)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:131)
keycloak | at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:33)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
keycloak | at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
keycloak | at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
keycloak | at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:82)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:147)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:84)
keycloak | at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:44)
keycloak | at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
keycloak | at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
keycloak | at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak | at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:58)
keycloak | at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:36)
keycloak | at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
keycloak | at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
keycloak | at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak | at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
keycloak | at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
keycloak | at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
keycloak | at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
keycloak | at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
keycloak | at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
keycloak | at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
keycloak | at java.base/java.lang.Thread.run(Thread.java:840)
Oh, what do we have here? A surprise guest: the CorsErrorResponseException! CORS errors, the all-time favorites (not).
But this time they give a us an interesting hint which relates to the first exception message. We can clearly see that the Token issuer is localhost instead of keycloak.
Failed to verify identity token: Invalid token issuer. Expected
'https://keycloak:8443/realms/myrealm', but was 'https://localhost:8443/realms/myrealm'
Solution
First, I will tell you how to fix this problem and if your curiosity remains aroused, I will tell you the reason behind this problem.
Simply go to your Keycloak Admin console -> Select your Realm -> Realm settings -> General -> Frontend url and enter
https://<your-container-name>:<port>
for example my keycloak was running in Docker in a named container called keycloak Because I was using docker-compose, my backend will reference the keycloak instance by container name. The port was :8443, so I had to set the Frontend url to:
https://keycloak:8443
This will override the base URL for frontend requests
This will override the base URL for frontend requests
This will override the base URL for frontend requests
There is a Chinese proverb which goes 重要的事情说三遍 and means "Say important things three times" because this solved the problem, which I am going to explain now.
Reason
The problem behind all that was that I was issuing my access token via Postman using localhost and therefore my token issuer was localhost and not keycloak.
https://localhost:8443/realms/myrealm/protocol/openid-connect/token
My backend had the environment variable KEYCLOAK_URL because it needs to reference the keycloak instance by container name to function properly.
KEYCLOAK_URL=https://keycloak:8443/realms/myrealm
With keycloak's Frontend url, you can override the base URL of your frontend requests and this will solve the problem.
"Skip the chatter, hand over the code!"
version: "3.8"
services:
postgres:
image: postgres:15-alpine
container_name: postgres-15
restart: always
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: jdbc:postgresql://localhost:5432/appdb
volumes:
- pg-data-volume:/var/lib/postgresql/data
- ./scripts/schema.sql:/docker-entrypoint-initdb.d/1.sql
- ./scripts/load-data.sql:/docker-entrypoint-initdb.d/2.sql
ports:
- "5432:5432"
keycloak:
image: quay.io/keycloak/keycloak:22.0.5
container_name: keycloak
ports:
- "8443:8443"
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloakdb
KC_DB_USERNAME: keycloak_user
KC_DB_PASSWORD: secret
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
volumes:
- ./cert/keycloak-cert.pem:/etc/keycloak-cert.pem
- ./cert/keycloak-private-key.pem:/etc/keycloak-key.pem
command: ['start',
'--hostname-strict=false',
'--https-certificate-file=/etc/keycloak-cert.pem',
'--https-certificate-key-file=/etc/keycloak-key.pem',
'--log-level=DEBUG']
depends_on:
- "postgres"
app:
image: <my-custom-image>
container_name: app
restart: always
ports:
- "8080:8080"
environment:
DB_USERNAME: admin
DB_PASSWORD: secret
DB_URI: jdbc:postgresql://postgres:5432/db
KEYCLOAK_URL: https://keycloak:8443/realms/myrealm
KEYCLOAK_CLIENT_ID: backend-service
KEYCLOAK_CLIENT_SECRET: secret
depends_on:
- "keycloak"
volumes:
pg-data-volume:



