{"record":{"id":"1e860a2fe2e77cde","repo":"SigNoz/signoz","slug":"codeunauthenticated-1e860a","errorCode":"CodeUnauthenticated","errorMessage":"invalid token pair","messagePattern":"invalid token pair","errorType":"error_code","errorClass":"errors SigNozError","httpStatus":401,"severity":"error","filePath":"pkg/types/authtypes/token.go","lineNumber":171,"sourceCode":"\tif !typ.RotatedAt.IsZero() && typ.RotatedAt.Before(time.Now().Add(-rotationInterval)) {\n\t\treturn errors.New(errors.TypeUnauthenticated, ErrCodeTokenRotationRequired, \"token needs to be rotated\")\n\t}\n\n\tif typ.RotatedAt.IsZero() && typ.CreatedAt.Before(time.Now().Add(-rotationInterval)) {\n\t\treturn errors.New(errors.TypeUnauthenticated, ErrCodeTokenRotationRequired, \"token needs to be rotated\")\n\t}\n\n\treturn nil\n}\n\nfunc (typ *Token) Rotate(accessTokenOrPrevAccessToken string, refreshTokenOrPrevRefreshToken string, rotationDuration time.Duration, idleDuration time.Duration, maxDuration time.Duration) error {\n\tif typ.PrevAccessToken == accessTokenOrPrevAccessToken && typ.PrevRefreshToken == refreshTokenOrPrevRefreshToken {\n\t\t// If the token has been rotated within the rotation duration, do nothing and return the same token.\n\t\tif !typ.RotatedAt.IsZero() && typ.RotatedAt.Before(time.Now().Add(-rotationDuration)) {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, \"invalid token pair\")\n\t}\n\n\tif typ.AccessToken != accessTokenOrPrevAccessToken || typ.RefreshToken != refreshTokenOrPrevRefreshToken {\n\t\treturn errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, \"invalid token pair\")\n\t}\n\n\tif err := typ.IsExpired(idleDuration, maxDuration); err != nil {\n\t\treturn err\n\t}\n\n\t// Generate new access and refresh tokens.\n\ttyp.PrevAccessToken = typ.AccessToken\n\ttyp.AccessToken = password.MustGenerate(32, 10, 0, true, true)\n\ttyp.PrevRefreshToken = typ.RefreshToken\n\ttyp.RefreshToken = password.MustGenerate(32, 12, 0, true, true)\n\n\t// Set the rotated at time.\n\ttyp.RotatedAt = time.Now()","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/SigNoz/signoz/blob/5069bf80b08f1f00d7e014eccc09902f9871004f/pkg/types/authtypes/token.go#L153-L189","documentation":"Returned by Token.Rotate when the caller presents the previous token pair but the token has already been rotated within the rotation window, so replaying the old pair is rejected. Protects against reuse of a consumed pair.","triggerScenarios":"Calling Rotate twice with the same old (prev) token pair within rotationDuration; or a retry of a partially-failed rotate request.","commonSituations":"Network retry after a successful-but-unacknowledged rotation, race between two processes rotating the same token, or a client not persisting the newly rotated pair.","solutions":["Persist the new token pair immediately after a successful rotate, before any retry","On this error, re-fetch the current token (e.g. from your store) and use it","Make rotate calls idempotent-safe by serializing them (lock) per token"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := tok.Rotate(at, rt, rot, idle, max)\nif err != nil && strings.Contains(err.Error(), \"invalid token pair\") {\n    // reload latest pair from store and retry once\n}","preventionTips":["Persist the new pair atomically right after rotation","Serialize rotate calls per token with a lock","Retry with backoff only after refreshing the stored pair"],"tags":["tokens","auth","rotation","race-condition","replay"],"backgroundTag":"invalid-token-pair","analyzedSha":"5069bf80b08f1f00d7e014eccc09902f9871004f","analyzedAt":"2026-08-28T06:22:12.824Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}