{"record":{"id":"b1e348cd8960311d","repo":"rancher/rancher","slug":"invalid-token-b1e348","errorCode":null,"errorMessage":"invalid token","messagePattern":"invalid token","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/providers/saml/saml_client.go","lineNumber":662,"sourceCode":"\n\treturn nil\n}\n\nfunc (s *Provider) getUserIdFromRelayStateCookie(r *http.Request) (string, error) {\n\tuserID := \"\"\n\t// The state is stored in a cookie, which has the relay state as the key and a JWT token containing the userID as the value\n\tif relayState := r.Form.Get(\"RelayState\"); relayState != \"\" {\n\t\trelayStateCookie := s.clientState.GetState(r, relayState)\n\t\tjwtParser := newJWTParser()\n\t\ttoken, err := jwtParser.Parse(relayStateCookie, func(t *jwt.Token) (any, error) {\n\t\t\tsecretBlock := x509.MarshalPKCS1PrivateKey(s.serviceProvider.Key)\n\t\t\treturn secretBlock, nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error parsing relay state token: %w\", err)\n\t\t}\n\t\tif !token.Valid {\n\t\t\treturn \"\", fmt.Errorf(\"invalid token\")\n\t\t}\n\t\tclaims := token.Claims.(jwt.MapClaims)\n\t\tuserID, _ = claims[rancherUserID].(string)\n\t}\n\n\treturn userID, nil\n}\n\nfunc newJWTParser() *jwt.Parser {\n\treturn jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name}))\n}\n\nfunc validateFinalRedirectURL(redirectURL string, rancherServerURL string) (string, error) {\n\tif redirectURL == \"\" {\n\t\treturn \"\", errors.New(\"redirect URL was not provided\")\n\t}\n\tparsed, err := url.Parse(redirectURL)\n\tif err != nil {","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/rancher/rancher/blob/932558d4e68565aff2d2f36e89ec4a391b06e7c5/pkg/auth/providers/saml/saml_client.go#L644-L680","documentation":"Defensive validity check after a successful golang-jwt Parse of the relay-state cookie. With golang-jwt, Parse already returns an error for invalid tokens, so token.Valid == false with err == nil is nearly unreachable in practice; this branch exists to guard against parser API changes or tokens that parse but carry no valid signature claim set. Hitting it means the library returned a token object it simultaneously refuses to vouch for.","triggerScenarios":"A relay-state cookie that parses without error but whose internal validation flags were never set (custom parser configuration, future jwt library behavior changes); logically it follows error 480's Parse succeeding.","commonSituations":"Almost never seen in production; if reported, it usually indicates a golang-jwt version change or a hand-crafted cookie that trips an edge case in the parser rather than a normal misconfiguration.","solutions":["Treat exactly like a parse failure: discard the relay state and restart the SAML login flow","Check the golang-jwt dependency version for behavior changes around token.Valid","Capture and log the raw cookie value length/claims to identify how a parsed-but-invalid token was produced"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isValidSignedState(token *jwt.Token, err error) bool {\n    return err == nil && token != nil && token.Valid\n}","tryCatchPattern":"token, err := jwtParser.Parse(relayStateCookie, keyFunc)\nif err != nil || !token.Valid {\n    return \"\", fmt.Errorf(\"relay state token rejected (err=%v valid=%v)\", err, token != nil && token.Valid)\n}","preventionTips":["Pin the golang-jwt version and review release notes when upgrading","Combine err and token.Valid in a single predicate as the handlers already do (saml_handlers.go:92)","Treat any failure here as an unrecoverable session and restart the login flow"],"tags":["saml","jwt","authentication","defensive-check"],"backgroundTag":null,"analyzedSha":"932558d4e68565aff2d2f36e89ec4a391b06e7c5","analyzedAt":"2026-08-16T04:37:02.125Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}