gotify/server · error

issuer claim was empty

Error message

issuer claim was empty

What it means

resolveUser throws 500 'issuer claim was empty' when the OIDC ID token claims have no issuer (iss) value. Gotify builds the OIDC identity as issuer#subject, so a missing issuer makes the identity unusable.

Source

Thrown at api/oidc.go:429

func (a *OIDCAPI) generateState() (string, error) {
	nonce := make([]byte, 20)
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}
	return hex.EncodeToString(nonce), nil
}

// resolveUser looks up, links, or creates the user bound to an OIDC identity.
//
//  1. Look up the user by OIDC id (<iss>#<sub>). If found, use it.
//  2. Otherwise look up a user by the username claim. If one exists, link it to
//     this OIDC identity, which requires GOTIFY_OIDC_LINK_BY_USERNAME and
//     that the user is not already bound to a different identity.
//  3. Otherwise auto-register a new user, which requires GOTIFY_OIDC_AUTOREGISTER.
func (a *OIDCAPI) resolveUser(idToken *oidc.IDTokenClaims, info *oidc.UserInfo) (*model.User, int, error) {
	issuer := idToken.GetIssuer()
	if issuer == "" {
		return nil, http.StatusInternalServerError, errors.New("issuer claim was empty")
	}
	if _, err := url.Parse(issuer); err != nil {
		return nil, http.StatusInternalServerError, fmt.Errorf("issuer url %q is not a valid url: %w", issuer, err)
	}
	if strings.Contains(issuer, "#") {
		return nil, http.StatusInternalServerError, fmt.Errorf("issuer url %q may not contain a fragment", issuer)
	}
	subject := info.GetSubject()
	if subject == "" {
		return nil, http.StatusInternalServerError, errors.New("subject claim was empty")
	}
	oidcID := issuer + "#" + subject

	user, err := a.DB.GetUserByOIDC(oidcID)
	if err != nil {
		return nil, http.StatusInternalServerError, fmt.Errorf("database error: %w", err)
	}

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Check the ID token (jwt.io) and ensure the 'iss' claim is present and matches the configured provider URL.
  2. Fix the provider's token issuance configuration.
  3. Obtain the ID token via the standard authorization-code flow instead of hand-crafting it.
  4. Verify GOTIFY_OIDC_ISSUER points to the correct provider.

Example fix

// before
{"sub":"123","aud":"app"} // no iss
// after
{"iss":"https://idp.example.com","sub":"123","aud":"app"}
Defensive patterns

Strategy: type-guard

Validate before calling

const claims = decodeJwt(idToken);
if (typeof claims.iss !== 'string' || claims.iss.length === 0) throw new Error('token missing iss claim');

Type guard

function hasIssuer(claims) { return typeof claims.iss === 'string' && claims.iss.trim() !== ''; }

Prevention

When it happens

Trigger: Calling ExternalTokenHandler (and thus resolveUser) with an ID token whose claims lack the iss claim, e.g. a malformed or hand-crafted token or a provider that omits iss.

Common situations: Misconfigured custom OIDC provider; testing with hand-built JWTs that omit 'iss'; an upstream proxy or token transformation stripping claims; using a token from a different endpoint than the intended provider.

Related errors


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/dd65d5cccd5d795d. Report an issue: GitHub.