keycloak/keycloak · error · VerificationException

Invalid token issuer. Expected '...'

Error message

Invalid token issuer. Expected '...'

What it means

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.

Source

Thrown at core/src/main/java/org/keycloak/TokenVerifier.java:114

    public static class RealmUrlCheck implements Predicate<JsonWebToken> {

        private static final RealmUrlCheck NULL_INSTANCE = new RealmUrlCheck(null);

        private final String realmUrl;

        public RealmUrlCheck(String realmUrl) {
            this.realmUrl = realmUrl;
        }

        @Override
        public boolean test(JsonWebToken t) throws VerificationException {
            if (this.realmUrl == null) {
                throw new VerificationException("Realm URL not set");
            }

            if (! this.realmUrl.equals(t.getIssuer())) {
                throw new VerificationException("Invalid token issuer. Expected '" + this.realmUrl + "'");
            }

            return true;
        }
    }

    public static class TokenTypeCheck implements Predicate<JsonWebToken> {

        private static final TokenTypeCheck INSTANCE_DEFAULT_TOKEN_TYPE = new TokenTypeCheck(Arrays.asList(TokenUtil.TOKEN_TYPE_BEARER));

        private final List<String> tokenTypes;

        public TokenTypeCheck(List<String> tokenTypes) {
            this.tokenTypes = tokenTypes;
        }

        @Override
        public boolean test(JsonWebToken t) throws VerificationException {

View on GitHub (pinned to 66c7e15a37)

Solutions

  1. Print the token's 'iss' claim and the configured realmUrl side by side; align them exactly.
  2. Ensure realmUrl includes the full /realms/{realm} suffix, e.g. https://host/realms/myrealm.
  3. If behind a proxy, set the Keycloak hostname/issuer settings (KC_HOSTNAME_URL / hostname.issuer) so the 'iss' claim matches what verifiers expect.
  4. Normalize trailing slashes and scheme (http/https) on both sides.

Example fix

// before: missing /realms/{realm} suffix
verifier.realmUrl("https://keycloak.example.com");

// after: full realm issuer URL
verifier.realmUrl("https://keycloak.example.com/realms/myrealm");
Defensive patterns

Strategy: validation

Validate before calling

// Normalize and compare issuers before verification
String expected = normalizeIssuer(config.get("realmIssuerUrl"));
String actual = parseUnsafe(token).getIssuer();
if (!expected.equals(actual)) {
  // reject or correct config before verifying
}
static String normalizeIssuer(String s) { return s == null ? null : s.replaceAll("/+$/, ""); }

Type guard

static boolean issuersMatch(String expected, String actual) {
  if (expected == null || actual == null) return false;
  return expected.replaceAll("/+$/", "").equals(actual.replaceAll("/+$/", ""));
}

Try / catch

try {
  verifier.realmUrl(realmUrl).verify();
} catch (VerificationException e) {
  if (e.getMessage().startsWith("Invalid token issuer")) {
    // log expected vs actual, correct config or proxy hostname settings
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of keycloak/keycloak@66c7e15a37 (2026-08-14). Data as JSON: /api/errors/31d32c4008049c94. Report an issue: GitHub.