From 32791eae1f0c8462e40f5d6e3423d1316aa19fdf Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Sun, 12 Oct 2025 20:20:31 +0530 Subject: [PATCH 1/3] fix: prevent and recover from JWT token corruption in keyring storage --- internal/cli/cli.go | 2 +- internal/cli/management.go | 19 ++++++++++++++++++ internal/keyring/keyring.go | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 6c82ef0a0..3a50d6f4a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -111,7 +111,7 @@ func (c *cli) setupWithAuthentication(ctx context.Context) error { } } - api, err := initializeManagementClient(tenant.Domain, tenant.GetAccessToken()) + api, err := initializeManagementClientWithTokenValidation(tenant) if err != nil { return err } diff --git a/internal/cli/management.go b/internal/cli/management.go index 5f0286c8f..840bb537a 100644 --- a/internal/cli/management.go +++ b/internal/cli/management.go @@ -3,6 +3,7 @@ package cli import ( "crypto/tls" "crypto/x509" + "errors" "fmt" "net/http" "net/url" @@ -11,10 +12,14 @@ import ( "strings" "time" + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/PuerkitoBio/rehttp" "github.com/auth0/go-auth0/management" "github.com/auth0/auth0-cli/internal/buildinfo" + "github.com/auth0/auth0-cli/internal/config" + "github.com/auth0/auth0-cli/internal/keyring" ) func initializeManagementClient(tenantDomain string, accessToken string) (*management.Management, error) { @@ -30,6 +35,20 @@ func initializeManagementClient(tenantDomain string, accessToken string) (*manag return client, err } +// with enhanced error handling for corrupted tokens. +func initializeManagementClientWithTokenValidation(tenant config.Tenant) (*management.Management, error) { + err := keyring.ValidateAccessToken(tenant.Domain) + if err != nil && errors.Is(err, keyring.ErrMalformedToken) { + return nil, fmt.Errorf("authentication token is corrupted, please run: %s\n\n%s", + ansi.Cyan("auth0 logout && auth0 login"), + ansi.Faint("Note: From version v1.18.0 onward, malformed tokens can no longer be generated due to improved token handling"), + ) + } + + accessToken := tenant.GetAccessToken() + return initializeManagementClient(tenant.Domain, accessToken) +} + func customClientWithRetries() *http.Client { client := &http.Client{ Transport: rateLimitTransport( diff --git a/internal/keyring/keyring.go b/internal/keyring/keyring.go index 6a946eca4..632007927 100644 --- a/internal/keyring/keyring.go +++ b/internal/keyring/keyring.go @@ -5,9 +5,15 @@ import ( "fmt" "strings" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/zalando/go-keyring" ) +var ( + // ErrMalformedToken indicates a corrupted JWT token was found in keyring. + ErrMalformedToken = errors.New("corrupted authentication token detected") +) + const ( secretRefreshToken = "Auth0 CLI Refresh Token" secretClientSecret = "Auth0 CLI Client Secret" @@ -71,6 +77,12 @@ func DeleteSecretsForTenant(tenant string) error { } func StoreAccessToken(tenant, value string) error { + // First, clear any existing chunks to prevent concatenation issues. + for i := 0; i < secretAccessTokenMaxChunks; i++ { + _ = keyring.Delete(fmt.Sprintf("%s %d", secretAccessToken, i), tenant) + } + + // Now store the new token in chunks. chunks := chunk(value, secretAccessTokenChunkSizeInBytes) for i := 0; i < len(chunks); i++ { @@ -102,6 +114,26 @@ func GetAccessToken(tenant string) (string, error) { return accessToken, nil } +func ValidateAccessToken(tenant string) error { + accessToken, err := GetAccessToken(tenant) + if err != nil { + return err + } + + // Validate that the reconstructed token looks like a JWT. + if accessToken == "" { + return nil + } + + // Use actual JWT parsing to validate the token structure. + if !isValidJWT(accessToken) { + return ErrMalformedToken + } + + // Token is valid. + return nil +} + func chunk(slice string, chunkSize int) []string { var chunks []string for i := 0; i < len(slice); i += chunkSize { @@ -118,3 +150,10 @@ func chunk(slice string, chunkSize int) []string { return chunks } + +// isValidJWT uses actual JWT parsing to validate the token. +func isValidJWT(token string) bool { + // Just check if it's a valid JWT structure. + _, err := jwt.ParseInsecure([]byte(token)) + return err == nil +} From a06067bf3bf5a6ea911bd2a613dbf11f78b6d807 Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Wed, 15 Oct 2025 13:46:31 +0530 Subject: [PATCH 2/3] enhance error handling for corrupted tokens --- internal/cli/cli.go | 9 ++++++++- internal/cli/management.go | 19 ------------------- internal/config/tenant.go | 15 ++++++++++++--- internal/keyring/keyring.go | 33 --------------------------------- 4 files changed, 20 insertions(+), 56 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 3a50d6f4a..8ef80e8ec 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -111,7 +111,14 @@ func (c *cli) setupWithAuthentication(ctx context.Context) error { } } - api, err := initializeManagementClientWithTokenValidation(tenant) + if errors.Is(err, config.ErrMalformedToken) { + return fmt.Errorf("authentication token is corrupted, please run: %s\n\n%s", + ansi.Cyan("auth0 logout && auth0 login"), + ansi.Yellow("Note: Token handling was enhanced in v1.18.0+ to prevent malformed tokens."), + ) + } + + api, err := initializeManagementClient(tenant.Domain, tenant.GetAccessToken()) if err != nil { return err } diff --git a/internal/cli/management.go b/internal/cli/management.go index 840bb537a..5f0286c8f 100644 --- a/internal/cli/management.go +++ b/internal/cli/management.go @@ -3,7 +3,6 @@ package cli import ( "crypto/tls" "crypto/x509" - "errors" "fmt" "net/http" "net/url" @@ -12,14 +11,10 @@ import ( "strings" "time" - "github.com/auth0/auth0-cli/internal/ansi" - "github.com/PuerkitoBio/rehttp" "github.com/auth0/go-auth0/management" "github.com/auth0/auth0-cli/internal/buildinfo" - "github.com/auth0/auth0-cli/internal/config" - "github.com/auth0/auth0-cli/internal/keyring" ) func initializeManagementClient(tenantDomain string, accessToken string) (*management.Management, error) { @@ -35,20 +30,6 @@ func initializeManagementClient(tenantDomain string, accessToken string) (*manag return client, err } -// with enhanced error handling for corrupted tokens. -func initializeManagementClientWithTokenValidation(tenant config.Tenant) (*management.Management, error) { - err := keyring.ValidateAccessToken(tenant.Domain) - if err != nil && errors.Is(err, keyring.ErrMalformedToken) { - return nil, fmt.Errorf("authentication token is corrupted, please run: %s\n\n%s", - ansi.Cyan("auth0 logout && auth0 login"), - ansi.Faint("Note: From version v1.18.0 onward, malformed tokens can no longer be generated due to improved token handling"), - ) - } - - accessToken := tenant.GetAccessToken() - return initializeManagementClient(tenant.Domain, accessToken) -} - func customClientWithRetries() *http.Client { client := &http.Client{ Transport: rateLimitTransport( diff --git a/internal/config/tenant.go b/internal/config/tenant.go index 91e3ecafb..de216a0c9 100644 --- a/internal/config/tenant.go +++ b/internal/config/tenant.go @@ -8,6 +8,8 @@ import ( "slices" "time" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/auth0/auth0-cli/internal/auth" "github.com/auth0/auth0-cli/internal/keyring" ) @@ -17,6 +19,8 @@ const accessTokenExpThreshold = 5 * time.Minute var ( // ErrInvalidToken is thrown when the token is invalid. ErrInvalidToken = errors.New("token is invalid") + // ErrMalformedToken indicates a corrupted JWT token was found in keyring. + ErrMalformedToken = errors.New("corrupted authentication token detected") ) type ErrTokenMissingRequiredScopes struct { @@ -115,11 +119,16 @@ func (t *Tenant) CheckAuthenticationStatus() error { } accessToken := t.GetAccessToken() - if accessToken != "" && !t.HasExpiredToken() { - return nil + if accessToken == "" || t.HasExpiredToken() { + return ErrInvalidToken + } + + // Validate that the access token is a well-formed JWT token. + if _, err := jwt.ParseInsecure([]byte(accessToken)); err != nil { + return ErrMalformedToken } - return ErrInvalidToken + return nil } // RegenerateAccessToken regenerates the access token for the tenant. diff --git a/internal/keyring/keyring.go b/internal/keyring/keyring.go index 632007927..321828c97 100644 --- a/internal/keyring/keyring.go +++ b/internal/keyring/keyring.go @@ -5,15 +5,9 @@ import ( "fmt" "strings" - "github.com/lestrrat-go/jwx/v2/jwt" "github.com/zalando/go-keyring" ) -var ( - // ErrMalformedToken indicates a corrupted JWT token was found in keyring. - ErrMalformedToken = errors.New("corrupted authentication token detected") -) - const ( secretRefreshToken = "Auth0 CLI Refresh Token" secretClientSecret = "Auth0 CLI Client Secret" @@ -114,26 +108,6 @@ func GetAccessToken(tenant string) (string, error) { return accessToken, nil } -func ValidateAccessToken(tenant string) error { - accessToken, err := GetAccessToken(tenant) - if err != nil { - return err - } - - // Validate that the reconstructed token looks like a JWT. - if accessToken == "" { - return nil - } - - // Use actual JWT parsing to validate the token structure. - if !isValidJWT(accessToken) { - return ErrMalformedToken - } - - // Token is valid. - return nil -} - func chunk(slice string, chunkSize int) []string { var chunks []string for i := 0; i < len(slice); i += chunkSize { @@ -150,10 +124,3 @@ func chunk(slice string, chunkSize int) []string { return chunks } - -// isValidJWT uses actual JWT parsing to validate the token. -func isValidJWT(token string) bool { - // Just check if it's a valid JWT structure. - _, err := jwt.ParseInsecure([]byte(token)) - return err == nil -} From 505dc5b51674cc5a1a031716a86a7e1f6f700323 Mon Sep 17 00:00:00 2001 From: ramya18101 Date: Thu, 16 Oct 2025 18:17:12 +0530 Subject: [PATCH 3/3] improve error handling when deleting client secret from keyring --- internal/keyring/keyring.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/keyring/keyring.go b/internal/keyring/keyring.go index 321828c97..bdb64a097 100644 --- a/internal/keyring/keyring.go +++ b/internal/keyring/keyring.go @@ -73,7 +73,11 @@ func DeleteSecretsForTenant(tenant string) error { func StoreAccessToken(tenant, value string) error { // First, clear any existing chunks to prevent concatenation issues. for i := 0; i < secretAccessTokenMaxChunks; i++ { - _ = keyring.Delete(fmt.Sprintf("%s %d", secretAccessToken, i), tenant) + if err := keyring.Delete(secretClientSecret, tenant); err != nil { + if !errors.Is(err, keyring.ErrNotFound) { + return fmt.Errorf("failed to delete client secret from keyring: %s", err) + } + } } // Now store the new token in chunks.