{"record":{"id":"85f1a82881d73274","repo":"fatedier/frp","slug":"invalid-oidc-token-in-login-v","errorCode":null,"errorMessage":"invalid OIDC token in login: %v","messagePattern":"invalid OIDC token in login: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oidc.go","lineNumber":301,"sourceCode":"\t\tSkipClientIDCheck: cfg.Audience == \"\",\n\t\tSkipExpiryCheck:   cfg.SkipExpiryCheck,\n\t\tSkipIssuerCheck:   cfg.SkipIssuerCheck,\n\t}\n\treturn provider.Verifier(&verifierConf)\n}\n\nfunc NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {\n\treturn &OidcAuthConsumer{\n\t\tadditionalAuthScopes: additionalAuthScopes,\n\t\tverifier:             verifier,\n\t\tsubjectsFromLogin:    make(map[string]struct{}),\n\t}\n}\n\nfunc (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {\n\ttoken, err := auth.verifier.Verify(context.Background(), loginMsg.PrivilegeKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid OIDC token in login: %v\", err)\n\t}\n\tauth.mu.Lock()\n\tauth.subjectsFromLogin[token.Subject] = struct{}{}\n\tauth.mu.Unlock()\n\treturn nil\n}\n\nfunc (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) {\n\ttoken, err := auth.verifier.Verify(context.Background(), privilegeKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid OIDC token in ping: %v\", err)\n\t}\n\tauth.mu.RLock()\n\t_, ok := auth.subjectsFromLogin[token.Subject]\n\tauth.mu.RUnlock()\n\tif !ok {\n\t\treturn fmt.Errorf(\"received different OIDC subject in login and ping. \"+\n\t\t\t\"new subject: %s\",","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/auth/oidc.go#L283-L319","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Decode the failing JWT (jwt.io or cut -d. -f2 | base64 -d) and check exp, iss, aud against frps's oidc config","Ensure frps's oidc.issuer (and audience, if set) exactly matches the claims in tokens frpc obtains","Sync clocks (NTP/chrony) on frps, frpc, and the IdP — expired-token failures are usually skew","If keys rotated, restart frps or otherwise refresh the JWKS; confirm the issuer still serves the JWKS endpoint","Check that frpc is actually sending an OIDC token, not a static auth token (mixed authentication.mode between sides)"],"exampleFix":"# frps before — issuer mismatch with tokens frpc gets\nauthentication.oidc.issuer = \"https://idp.example.com\"\n\n# after — exact issuer including realm path\nauthentication.oidc.issuer = \"https://idp.example.com/realms/frp\"\nauthentication.oidc.audience = \"frp-client\"","handlingStrategy":"validation","validationCode":"// client-side preflight before sending Login\ntok, _, err := jwt.NewParser().ParseUnverified(privilegeKey, jwt.MapClaims{})\nif err == nil {\n    if exp, ok := tok.Claims.(jwt.MapClaims)[\"exp\"].(float64); ok && time.Until(time.Unix(int64(exp), 0)) < 30*time.Second {\n        return errors.New(\"token about to expire; refresh before login\")\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := consumer.VerifyLogin(loginMsg); err != nil {\n    log.Warnf(\"oidc login rejected: %v\", err)\n    return err // do not retry without a fresh token; re-login with new credentials instead\n}","preventionTips":["Pin frps oidc.issuer to the exact realm URL the IdP advertises","Run NTP on frps, frpc, and the IdP","Alert on JWKS fetch failures so key rotations are noticed early"],"tags":["frp","oidc","jwt","verification","authentication"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}