fatedier/frp · error

invalid OIDC token in login: %v

Error message

invalid OIDC token in login: %v

What it means

frps-side verification of an OIDC login. OidcAuthConsumer.VerifyLogin passes loginMsg.PrivilegeKey (the JWT from frpc) to the configured TokenVerifier (typically a Keycloak/oidc IDTokenVerifier). Any JWT validation failure — bad signature, expired token, wrong issuer or audience, malformed token — is wrapped with this message. On success the token's subject is recorded for later ping cross-checks (see error 146).

Source

Thrown at pkg/auth/oidc.go:301

		SkipClientIDCheck: cfg.Audience == "",
		SkipExpiryCheck:   cfg.SkipExpiryCheck,
		SkipIssuerCheck:   cfg.SkipIssuerCheck,
	}
	return provider.Verifier(&verifierConf)
}

func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
	return &OidcAuthConsumer{
		additionalAuthScopes: additionalAuthScopes,
		verifier:             verifier,
		subjectsFromLogin:    make(map[string]struct{}),
	}
}

func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
	token, err := auth.verifier.Verify(context.Background(), loginMsg.PrivilegeKey)
	if err != nil {
		return fmt.Errorf("invalid OIDC token in login: %v", err)
	}
	auth.mu.Lock()
	auth.subjectsFromLogin[token.Subject] = struct{}{}
	auth.mu.Unlock()
	return nil
}

func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) {
	token, err := auth.verifier.Verify(context.Background(), privilegeKey)
	if err != nil {
		return fmt.Errorf("invalid OIDC token in ping: %v", err)
	}
	auth.mu.RLock()
	_, ok := auth.subjectsFromLogin[token.Subject]
	auth.mu.RUnlock()
	if !ok {
		return fmt.Errorf("received different OIDC subject in login and ping. "+
			"new subject: %s",

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Decode the failing JWT (jwt.io or cut -d. -f2 | base64 -d) and check exp, iss, aud against frps's oidc config
  2. Ensure frps's oidc.issuer (and audience, if set) exactly matches the claims in tokens frpc obtains
  3. Sync clocks (NTP/chrony) on frps, frpc, and the IdP — expired-token failures are usually skew
  4. If keys rotated, restart frps or otherwise refresh the JWKS; confirm the issuer still serves the JWKS endpoint
  5. Check that frpc is actually sending an OIDC token, not a static auth token (mixed authentication.mode between sides)

Example fix

# frps before — issuer mismatch with tokens frpc gets
authentication.oidc.issuer = "https://idp.example.com"

# after — exact issuer including realm path
authentication.oidc.issuer = "https://idp.example.com/realms/frp"
authentication.oidc.audience = "frp-client"
Defensive patterns

Strategy: validation

Validate before calling

// client-side preflight before sending Login
tok, _, err := jwt.NewParser().ParseUnverified(privilegeKey, jwt.MapClaims{})
if err == nil {
    if exp, ok := tok.Claims.(jwt.MapClaims)["exp"].(float64); ok && time.Until(time.Unix(int64(exp), 0)) < 30*time.Second {
        return errors.New("token about to expire; refresh before login")
    }
}

Try / catch

if err := consumer.VerifyLogin(loginMsg); err != nil {
    log.Warnf("oidc login rejected: %v", err)
    return err // do not retry without a fresh token; re-login with new credentials instead
}

Prevention

When it happens

Trigger: frpc logs in with a PrivilegeKey JWT that: is expired by the time frps verifies it; was signed with a key not in the JWKS from the configured issuer; has iss/aud claims that don't match frps's oidc configuration; or is not a valid JWT at all.

Common situations: frps and frpc point at different IdP realms or different issuer URLs; large clock skew between frpc, frps, and the IdP; the IdP rotated signing keys and frps has a stale JWKS cache; audience/clientid mismatch between the two sides.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/85f1a82881d73274. Report an issue: GitHub.