{"record":{"id":"31d32c4008049c94","repo":"keycloak/keycloak","slug":"invalid-token-issuer-expected","errorCode":null,"errorMessage":"Invalid token issuer. Expected '...'","messagePattern":"Invalid token issuer\\. Expected '\\.\\.\\.'","errorType":"exception","errorClass":"VerificationException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/keycloak/TokenVerifier.java","lineNumber":114,"sourceCode":"\n    public static class RealmUrlCheck implements Predicate<JsonWebToken> {\n\n        private static final RealmUrlCheck NULL_INSTANCE = new RealmUrlCheck(null);\n\n        private final String realmUrl;\n\n        public RealmUrlCheck(String realmUrl) {\n            this.realmUrl = realmUrl;\n        }\n\n        @Override\n        public boolean test(JsonWebToken t) throws VerificationException {\n            if (this.realmUrl == null) {\n                throw new VerificationException(\"Realm URL not set\");\n            }\n\n            if (! this.realmUrl.equals(t.getIssuer())) {\n                throw new VerificationException(\"Invalid token issuer. Expected '\" + this.realmUrl + \"'\");\n            }\n\n            return true;\n        }\n    }\n\n    public static class TokenTypeCheck implements Predicate<JsonWebToken> {\n\n        private static final TokenTypeCheck INSTANCE_DEFAULT_TOKEN_TYPE = new TokenTypeCheck(Arrays.asList(TokenUtil.TOKEN_TYPE_BEARER));\n\n        private final List<String> tokenTypes;\n\n        public TokenTypeCheck(List<String> tokenTypes) {\n            this.tokenTypes = tokenTypes;\n        }\n\n        @Override\n        public boolean test(JsonWebToken t) throws VerificationException {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/keycloak/keycloak/blob/66c7e15a3788de7764f07dd2558275a02770e16d/core/src/main/java/org/keycloak/TokenVerifier.java#L96-L132","documentation":"Thrown by RealmUrlCheck.test when the token's issuer ('iss' claim) does not equal the configured realmUrl. The check performs an exact string equality, so any trailing slash, scheme difference, realm-name casing, or host alias mismatch causes rejection. The expected value is interpolated into the message for diagnosis.","triggerScenarios":"Verifying a token whose 'iss' claim differs in any way from the realmUrl supplied to TokenVerifier.realmUrl(...). Common triggers: realmUrl set to the base Keycloak URL without the /realms/{realm} path, an http vs https mismatch, a trailing-slash difference, or verifying a token from realm A against realm B's URL.","commonSituations":"Frontend behind a reverse proxy where the issuer claim uses the external URL but the verifier is configured with the internal URL, realm renames, or copying verifier config between environments without updating the issuer.","solutions":["Print the token's 'iss' claim and the configured realmUrl side by side; align them exactly.","Ensure realmUrl includes the full /realms/{realm} suffix, e.g. https://host/realms/myrealm.","If behind a proxy, set the Keycloak hostname/issuer settings (KC_HOSTNAME_URL / hostname.issuer) so the 'iss' claim matches what verifiers expect.","Normalize trailing slashes and scheme (http/https) on both sides."],"exampleFix":"// before: missing /realms/{realm} suffix\nverifier.realmUrl(\"https://keycloak.example.com\");\n\n// after: full realm issuer URL\nverifier.realmUrl(\"https://keycloak.example.com/realms/myrealm\");","handlingStrategy":"validation","validationCode":"// Normalize and compare issuers before verification\nString expected = normalizeIssuer(config.get(\"realmIssuerUrl\"));\nString actual = parseUnsafe(token).getIssuer();\nif (!expected.equals(actual)) {\n  // reject or correct config before verifying\n}\nstatic String normalizeIssuer(String s) { return s == null ? null : s.replaceAll(\"/+$/, \"\"); }","typeGuard":"static boolean issuersMatch(String expected, String actual) {\n  if (expected == null || actual == null) return false;\n  return expected.replaceAll(\"/+$/\", \"\").equals(actual.replaceAll(\"/+$/\", \"\"));\n}","tryCatchPattern":"try {\n  verifier.realmUrl(realmUrl).verify();\n} catch (VerificationException e) {\n  if (e.getMessage().startsWith(\"Invalid token issuer\")) {\n    // log expected vs actual, correct config or proxy hostname settings\n  } else throw e;\n}","preventionTips":["Configure the Keycloak hostname/issuer so the 'iss' claim matches what verifiers expect.","Normalize trailing slashes and http/https on both sides before comparison.","Print the token's 'iss' claim during integration tests to catch mismatches early."],"tags":["jwt","verification","issuer","realm","mismatch"],"backgroundTag":null,"analyzedSha":"66c7e15a3788de7764f07dd2558275a02770e16d","analyzedAt":"2026-08-14T01:36:42.651Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}