Repository: auth0/terraform-provider-auth0

Issue:
Fallback to CLI access token from config file
### Checklist

- [x] I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).

### Describe the problem you'd like to have solved

Our Terraform deployment pipeline does not support Keyring, but we would like to use Auth0 CLI to login into all the tenants we need, so we can simplify our Terraform setup to apply similar configuration to multiple tenant from a single Terraform run. 

### Describe the ideal solution

The CLI stores the access token in `~/.config/auth0/config.json` file when keyring fails. The Terraform provider should use the access token from config file when keyrings fails.

Option could be to share configuration code somehow. 

### Alternatives and current workarounds

Any option could also be to spawn `auth0` command to get the actual access token from the CLI. 

### Additional context

_No response_

Discussion:
@ErwinSteffens: Created PR for this: https://github.com/auth0/terraform-provider-auth0/pull/1381@ErwinSteffens: @duedares-rvj I see you assigned this to yourself. There is PR for it. Could you check it out?

We would like to know if we can use this of make some different plan for our pipeline. @duedares-rvj: @ErwinSteffens Hello 👋 
We did create a internal task to review your contribution for extending the cli_login functionality.  
Currently, the team is occupied with certain deliverables but I wanted to ensure you that your PR has our attention. :)

We shall revert back soon!
As usual, your contribution is invaluable to us. 
Regards@ErwinSteffens: Any update on this one?@duedares-rvj: @ErwinSteffens  Added a comment on your contribution PR. We are planning for a release soon and would like this change to be included in that. :)@duedares-rvj: Believe this is good to close now since your PR is merged.
Thank you again @ErwinSteffens  🎊 

Solution patch:
diff --git a/internal/config/config.go b/internal/config/config.go
index c073a874..d9740527 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,6 +10,8 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"os"
+	"path"
 	"regexp"
 	"strconv"
 	"strings"
@@ -37,6 +39,22 @@ const secretAccessTokenMaxChunks = 50
 
 var version = "dev"
 
+// cliConfigPath is the default path to the auth0-cli config file.
+var cliConfigPath = path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json")
+
+// SetCliConfigPath sets the path to the auth0-cli config file.
+func SetCliConfigPath(p string) {
+	cliConfigPath = p
+}
+
+// CliConfig holds CLI configuration settings.
+type CliConfig struct {
+	Tenants map[string]struct {
+		AccessToken string    `json:"access_token,omitempty"`
+		ExpiresAt   time.Time `json:"expires_at"`
+	} `json:"tenants"`
+}
+
 // Config is the type used for the
 // *schema.Provider meta parameter.
 type Config struct {
@@ -77,120 +95,138 @@ func (c *Config) GetMutex() *mutex.KeyValue {
 	return c.mutex
 }
 
-type authConfig struct {
-	clientID                  string
-	clientSecret              string
-	apiToken                  string
-	audience                  string
-	clientAssertionPrivateKey string
-	clientAssertionSigningAlg string
-	customDomainHeader        string
+// ProviderConfig holds the loaded provider configuration values.
+type ProviderConfig struct {
+	Debug                     bool
+	Domain                    string
+	ClientID                  string
+	ClientSecret              string
+	APIToken                  string
+	Audience                  string
+	ClientAssertionPrivateKey string
+	ClientAssertionSigningAlg string
+	CustomDomainHeader        string
 }
 
-// ConfigureProvider will configure the *schema.Provider so that
-// *management.Management client and *mutex.KeyValue is stored
-// and passed into the subsequent resources as the meta parameter.
-func ConfigureProvider(terraformVersion *string) schema.ConfigureContextFunc {
-	return func(_ context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) {
-		config := authConfig{
-			clientID:                  data.Get("client_id").(string),
-			clientSecret:              data.Get("client_secret").(string),
-			apiToken:                  data.Get("api_token").(string),
-			audience:                  data.Get("audience").(string),
-			clientAssertionPrivateKey: data.Get("client_assertion_private_key").(string),
-			clientAssertionSigningAlg: data.Get("client_assertion_signing_alg").(string),
-			customDomainHeader:        data.Get("custom_domain_header").(string),
+// ParseResourceConfigData parses the *schema.ResourceData.
+func ParseResourceConfigData(data *schema.ResourceData) (ProviderConfig, diag.Diagnostics) {
+	cfg := ProviderConfig{
+		Domain:                    data.Get("domain").(string),
+		Debug:                     data.Get("debug").(bool),
+		ClientID:                  data.Get("client_id").(string),
+		ClientSecret:              data.Get("client_secret").(string),
+		APIToken:                  data.Get("api_token").(string),
+		Audience:                  data.Get("audience").(string),
+		ClientAssertionPrivateKey: data.Get("client_assertion_private_key").(string),
+		ClientAssertionSigningAlg: data.Get("client_assertion_signing_alg").(string),
+		CustomDomainHeader:        data.Get("custom_domain_header").(string),
+	}
+
+	dynamicCredentials := data.Get("dynamic_credentials").(bool)
+	cliLogin := data.Get("cli_login").(bool)
+
+	// Helper to return missing domain diagnostic.
+	missingDomain := func(vars string) diag.Diagnostics {
+		return diag.Diagnostics{{
+			Severity: diag.Error,
+			Summary:  "Missing required configuration",
+			Detail:   fmt.Sprintf("The 'AUTH0_DOMAIN' must be specified along with %s.", vars),
+		}}
+	}
+
+	switch {
+	case dynamicCredentials:
+		if cfg.Domain == "" {
+			return ProviderConfig{}, missingDomain("'AUTH0_DYNAMIC_CREDENTIALS'")
+		}
+
+	case cliLogin:
+		if cfg.Domain == "" {
+			return ProviderConfig{}, missingDomain("'AUTH0_CLI_LOGIN'")
+		}
+
+		// Fetch and validate CLI token.
+		tempToken, diags := fetchAndValidateCLIToken(cfg.Domain)
+		if diags != nil {
+			return ProviderConfig{}, diags
+		}
+
+		// Set the apiToken to the valid tempToken.
+		cfg.APIToken = tempToken
+
+	case cfg.APIToken != "":
+		if cfg.Domain == "" {
+			return ProviderConfig{}, missingDomain("'AUTH0_API_TOKEN'")
 		}
 
-		domain := data.Get("domain").(string)
-		debug := data.Get("debug").(bool)
-		dynamicCredentials := data.Get("dynamic_credentials").(bool)
-		cliLogin := data.Get("cli_login").(bool)
+	case cfg.ClientID != "":
+		var (
+			hasSecret    = cfg.ClientSecret != ""
+			hasAssertion = cfg.ClientAssertionPrivateKey != "" && cfg.ClientAssertionSigningAlg != ""
+		)
 
-		// Helper to return missing domain diagnostic.
-		missingDomain := func(vars string) diag.Diagnostics {
-			return diag.Diagnostics{{
+		if !hasSecret && !hasAssertion {
+			return ProviderConfig{}, diag.Diagnostics{{
 				Severity: diag.Error,
 				Summary:  "Missing required configuration",
-				Detail:   fmt.Sprintf("The 'AUTH0_DOMAIN' must be specified along with %s.", vars),
+				Detail: "When 'AUTH0_CLIENT_ID' is provided, either 'AUTH0_CLIENT_SECRET' or both " +
+					"'AUTH0_CLIENT_ASSERTION_PRIVATE_KEY' and 'AUTH0_CLIENT_ASSERTION_SIGNING_ALG' must also be specified.",
 			}}
 		}
-		switch {
-		case dynamicCredentials:
-			if domain == "" {
-				return nil, missingDomain("'AUTH0_DYNAMIC_CREDENTIALS'")
-			}
-		case cliLogin:
-			if domain == "" {
-				return nil, missingDomain("'AUTH0_CLI_LOGIN'")
-			}
 
-			// Fetch and validate CLI token.
-			tempToken, diags := fetchAndValidateCLIToken(domain)
-			if diags != nil {
-				return nil, diags
+		if cfg.Domain == "" {
+			switch {
+			case hasSecret:
+				return ProviderConfig{}, missingDomain("'AUTH0_CLIENT_ID' and 'AUTH0_CLIENT_SECRET'")
+			case hasAssertion:
+				return ProviderConfig{}, missingDomain("'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_ASSERTION_PRIVATE_KEY', and 'AUTH0_CLIENT_ASSERTION_SIGNING_ALG'")
 			}
+		}
 
-			// Set the apiToken to the valid tempToken.
-			config.apiToken = tempToken
-		case config.apiToken != "":
-			if domain == "" {
-				return nil, missingDomain("'AUTH0_API_TOKEN'")
-			}
-		case config.clientID != "":
-			var (
-				hasSecret    = config.clientSecret != ""
-				hasAssertion = config.clientAssertionPrivateKey != "" && config.clientAssertionSigningAlg != ""
-			)
+	default:
+		return ProviderConfig{}, diag.Diagnostics{{
+			Severity: diag.Error,
+			Summary:  "Missing environment variables",
+			Detail: "AUTH0_DOMAIN is required. Then, configure either AUTH0_API_TOKEN, " +
+				"or AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET, " +
+				"or AUTH0_CLIENT_ID, AUTH0_CLIENT_ASSERTION_PRIVATE_KEY, and AUTH0_CLIENT_ASSERTION_SIGNING_ALG, " +
+				"or enable CLI login with AUTH0_CLI_LOGIN=true.",
+		}}
+	}
 
-			if !hasSecret && !hasAssertion {
-				return nil, diag.Diagnostics{{
-					Severity: diag.Error,
-					Summary:  "Missing required configuration",
-					Detail: "When 'AUTH0_CLIENT_ID' is provided, either 'AUTH0_CLIENT_SECRET' or both " +
-						"'AUTH0_CLIENT_ASSERTION_PRIVATE_KEY' and 'AUTH0_CLIENT_ASSERTION_SIGNING_ALG' must also be specified.",
-				}}
-			}
+	return cfg, nil
+}
 
-			if domain == "" {
-				switch {
-				case hasSecret:
-					return nil, missingDomain("'AUTH0_CLIENT_ID' and 'AUTH0_CLIENT_SECRET'")
-				case hasAssertion:
-					return nil, missingDomain("'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_ASSERTION_PRIVATE_KEY', and 'AUTH0_CLIENT_ASSERTION_SIGNING_ALG'")
-				}
-			}
-		default:
-			return nil, diag.Diagnostics{{
-				Severity: diag.Error,
-				Summary:  "Missing environment variables",
-				Detail: "AUTH0_DOMAIN is required. Then, configure either AUTH0_API_TOKEN, " +
-					"or AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET, " +
-					"or AUTH0_CLIENT_ID, AUTH0_CLIENT_ASSERTION_PRIVATE_KEY, and AUTH0_CLIENT_ASSERTION_SIGNING_ALG, " +
-					"or enable CLI login with AUTH0_CLI_LOGIN=true.",
-			}}
+// ConfigureProvider will configure the *schema.Provider so that
+// *management.Management client and *mutex.KeyValue is stored
+// and passed into the subsequent resources as the meta parameter.
+func ConfigureProvider(terraformVersion *string) schema.ConfigureContextFunc {
+	return func(_ context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) {
+		config, d := ParseResourceConfigData(data)
+		if d != nil {
+			return nil, d
 		}
 
-		apiClient, err := management.New(domain,
+		apiClient, err := management.New(config.Domain,
 			authenticationOption(config),
-			management.WithDebug(debug),
+			management.WithDebug(config.Debug),
 			management.WithUserAgent(userAgent(terraformVersion)),
 			management.WithAuth0ClientEnvEntry(providerName, version),
 			management.WithNoRetries(),
 			management.WithClient(customClientWithRetries()),
-			management.WithCustomDomainHeader(config.customDomainHeader))
+			management.WithCustomDomainHeader(config.CustomDomainHeader))
 
 		if err != nil {
 			return nil, diag.FromErr(err)
 		}
 
-		apiClientV2, err := managementv2.New(domain,
+		apiClientV2, err := managementv2.New(config.Domain,
 			authenticationOptionV2(config),
-			option.WithDebug(debug),
+			option.WithDebug(config.Debug),
 			option.WithUserAgent(userAgent(terraformVersion)),
 			option.WithAuth0ClientEnvEntry(providerName, version),
 			option.WithHTTPClient(customClientWithRetries()),
-			option.WithCustomDomainHeader(config.customDomainHeader))
+			option.WithCustomDomainHeader(config.CustomDomainHeader))
 
 		if err != nil {
 			return nil, diag.FromErr(err)
@@ -210,6 +246,21 @@ func fetchAndValidateCLIToken(domain string) (string, diag.Diagnostics) {
 		tempToken += chunk
 	}
 
+	// If no token was found, try to fetch token from CLI config file.
+	if tempToken == "" {
+		configToken, err := getAccessTokenFromCliConfigFile(domain)
+		if err != nil {
+			return "", diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Failed to read config.json file",
+				Detail:   err.Error(),
+			}}
+		}
+
+		tempToken = configToken
+	}
+
+	// If no token was found, error out.
 	if tempToken == "" {
 		return "", diag.Diagnostics{{
 			Severity: diag.Error,
@@ -299,74 +350,74 @@ func userAgent(terraformVersion *string) string {
 }
 
 // authenticationOption computes the desired authentication option for the *management.Management client.
-func authenticationOption(cfg authConfig) management.Option {
+func authenticationOption(cfg ProviderConfig) management.Option {
 	ctx := context.Background()
 
 	switch {
-	case cfg.apiToken != "":
-		return management.WithStaticToken(cfg.apiToken)
-	case cfg.audience != "":
-		if cfg.clientAssertionPrivateKey != "" {
+	case cfg.APIToken != "":
+		return management.WithStaticToken(cfg.APIToken)
+	case cfg.Audience != "":
+		if cfg.ClientAssertionPrivateKey != "" {
 			return management.WithClientCredentialsPrivateKeyJwtAndAudience(
 				ctx,
-				cfg.clientID,
-				cfg.clientAssertionPrivateKey,
-				cfg.clientAssertionSigningAlg,
-				cfg.audience,
+				cfg.ClientID,
+				cfg.ClientAssertionPrivateKey,
+				cfg.ClientAssertionSigningAlg,
+				cfg.Audience,
 			)
 		}
 		return management.WithClientCredentialsAndAudience(
 			ctx,
-			cfg.clientID,
-			cfg.clientSecret,
-			cfg.audience,
+			cfg.ClientID,
+			cfg.ClientSecret,
+			cfg.Audience,
 		)
-	case cfg.clientAssertionPrivateKey != "":
+	case cfg.ClientAssertionPrivateKey != "":
 		return management.WithClientCredentialsPrivateKeyJwt(
 			ctx,
-			cfg.clientID,
-			cfg.clientAssertionPrivateKey,
-			cfg.clientAssertionSigningAlg,
+			cfg.ClientID,
+			cfg.ClientAssertionPrivateKey,
+			cfg.ClientAssertionSigningAlg,
 		)
 
 	default:
-		return management.WithClientCredentials(ctx, cfg.clientID, cfg.clientSecret)
+		return management.WithClientCredentials(ctx, cfg.ClientID, cfg.ClientSecret)
 	}
 }
 
 // authenticationOptionV2 computes the desired authentication option for the v2 management client.
-func authenticationOptionV2(cfg authConfig) option.RequestOption {
+func authenticationOptionV2(cfg ProviderConfig) option.RequestOption {
 	ctx := context.Background()
 
 	switch {
-	case cfg.apiToken != "":
-		return option.WithToken(cfg.apiToken)
-	case cfg.audience != "":
-		if cfg.clientAssertionPrivateKey != "" {
+	case cfg.APIToken != "":
+		return option.WithToken(cfg.APIToken)
+	case cfg.Audience != "":
+		if cfg.ClientAssertionPrivateKey != "" {
 			return option.WithClientCredentialsPrivateKeyJwtAndAudience(
 				ctx,
-				cfg.clientID,
-				cfg.clientAssertionPrivateKey,
-				cfg.clientAssertionSigningAlg,
-				cfg.audience,
+				cfg.ClientID,
+				cfg.ClientAssertionPrivateKey,
+				cfg.ClientAssertionSigningAlg,
+				cfg.Audience,
 			)
 		}
 		return option.WithClientCredentialsAndAudience(
 			ctx,
-			cfg.clientID,
-			cfg.clientSecret,
-			cfg.audience,
+			cfg.ClientID,
+			cfg.ClientSecret,
+			cfg.Audience,
 		)
-	case cfg.clientAssertionPrivateKey != "":
+	case cfg.ClientAssertionPrivateKey != "":
 		return option.WithClientCredentialsPrivateKeyJwt(
 			ctx,
-			cfg.clientID,
-			cfg.clientAssertionPrivateKey,
-			cfg.clientAssertionSigningAlg,
+			cfg.ClientID,
+			cfg.ClientAssertionPrivateKey,
+			cfg.ClientAssertionSigningAlg,
 		)
 
 	default:
-		return option.WithClientCredentials(ctx, cfg.clientID, cfg.clientSecret)
+		return option.WithClientCredentials(ctx, cfg.ClientID, cfg.ClientSecret)
 	}
 }
 
@@ -458,3 +509,28 @@ func retryableErrorRetryFunc(err error) bool {
 	// The error is likely recoverable so retry.
 	return true
 }
+
+// getAccessTokenFromCliConfigFile reads the Auth0 CLI config file and returns the access token for the given tenant.
+func getAccessTokenFromCliConfigFile(domain string) (string, error) {
+	if _, err := os.Stat(cliConfigPath); os.IsNotExist(err) {
+		return "", nil
+	}
+
+	buffer, err := os.ReadFile(path.Clean(cliConfigPath))
+	if err != nil {
+		return "", fmt.Errorf("failed to read config.json: %w", err)
+	}
+
+	c := &CliConfig{}
+	err = json.Unmarshal(buffer, c)
+	if err != nil {
+		return "", fmt.Errorf("failed to parse config.json: %w", err)
+	}
+
+	tenant, ok := c.Tenants[domain]
+	if !ok {
+		return "", nil
+	}
+
+	return tenant.AccessToken, nil
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 1cdca862..a2016586 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/eventstream"
 	userattributeprofile "github.com/auth0/terraform-provider-auth0/internal/auth0/user_attribute_profile"
+	"github.com/auth0/terraform-provider-auth0/internal/config"
 
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/networkacl"
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/outboundips"
@@ -40,7 +41,6 @@ import (
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/signingkey"
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/tenant"
 	"github.com/auth0/terraform-provider-auth0/internal/auth0/user"
-	"github.com/auth0/terraform-provider-auth0/internal/config"
 )
 
 // New returns a *schema.Provider.



Test patch:
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 96bdd392..84b4c8ce 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -2,17 +2,22 @@ package config_test
 
 import (
 	"context"
+	"os"
 	"testing"
+	"time"
 
 	"github.com/auth0/go-auth0"
 	"github.com/auth0/go-auth0/management"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/lestrrat-go/jwx/v2/jwt"
 	"github.com/stretchr/testify/assert"
+	"github.com/zalando/go-keyring"
 
 	"github.com/auth0/terraform-provider-auth0/internal/config"
-	"github.com/auth0/terraform-provider-auth0/internal/mutex"
 	"github.com/auth0/terraform-provider-auth0/internal/provider"
+
+	"github.com/auth0/terraform-provider-auth0/internal/mutex"
 )
 
 func TestConfigureProvider(t *testing.T) {
@@ -116,3 +121,228 @@ func TestConfigureProvider(t *testing.T) {
 		})
 	}
 }
+
+func TestParseResourceConfigData(t *testing.T) {
+	jwtToken := createJwtToken(t)
+	f := createCliConfigFile(t, jwtToken)
+	config.SetCliConfigPath(f.Name())
+	defer func(name string) {
+		_ = os.Remove(name)
+	}(f.Name())
+
+	var testCases = []struct {
+		name                 string
+		givenTerraformConfig map[string]interface{}
+		keyringAccessToken   string
+		configAccessToken    string
+		expectedDiagnostics  diag.Diagnostics
+		expectedConfig       config.ProviderConfig
+	}{
+		{
+			name: "it parses all auth configuration options",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":                       "example.auth0.com",
+				"debug":                        true,
+				"client_id":                    "1234567",
+				"client_secret":                "secret",
+				"api_token":                    "api-token",
+				"audience":                     "the-audience",
+				"client_assertion_private_key": "private-key",
+				"client_assertion_signing_alg": "signing-alg",
+				"custom_domain_header":         "custom-domain",
+			},
+			expectedDiagnostics: nil,
+			expectedConfig: config.ProviderConfig{
+				Domain:                    "example.auth0.com",
+				Debug:                     true,
+				ClientID:                  "1234567",
+				ClientSecret:              "secret",
+				APIToken:                  "api-token",
+				Audience:                  "the-audience",
+				ClientAssertionPrivateKey: "private-key",
+				ClientAssertionSigningAlg: "signing-alg",
+				CustomDomainHeader:        "custom-domain",
+			},
+		},
+		{
+			name: "it returns an error when dynamic_credentials is set and domain is empty",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":              "",
+				"dynamic_credentials": true,
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Missing required configuration",
+				Detail:   "The 'AUTH0_DOMAIN' must be specified along with 'AUTH0_DYNAMIC_CREDENTIALS'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name: "it returns an error when dynamic_credentials is set and domain is empty",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":    "",
+				"api_token": true,
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Missing required configuration",
+				Detail:   "The 'AUTH0_DOMAIN' must be specified along with 'AUTH0_API_TOKEN'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name: "it returns an error when client_id with client_secret is set and domain is empty ",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":        "",
+				"client_id":     "client-id",
+				"client_secret": "secret",
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Missing required configuration",
+				Detail:   "The 'AUTH0_DOMAIN' must be specified along with 'AUTH0_CLIENT_ID' and 'AUTH0_CLIENT_SECRET'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name: "it returns an error when client_id, client_assertion_private_key and client_assertion_signing_alg is set and domain is empty ",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":                       "",
+				"client_id":                    "client-id",
+				"client_assertion_private_key": "key",
+				"client_assertion_signing_alg": "alg",
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Missing required configuration",
+				Detail:   "The 'AUTH0_DOMAIN' must be specified along with 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_ASSERTION_PRIVATE_KEY', and 'AUTH0_CLIENT_ASSERTION_SIGNING_ALG'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name: "it returns an error when cli_login is set and domain is empty",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":    "",
+				"cli_login": true,
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Missing required configuration",
+				Detail:   "The 'AUTH0_DOMAIN' must be specified along with 'AUTH0_CLI_LOGIN'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name: "it loads access token from keyring when cli_login is set",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":    "example-token.auth0.com",
+				"cli_login": true,
+			},
+			keyringAccessToken:  jwtToken,
+			expectedDiagnostics: nil,
+			expectedConfig: config.ProviderConfig{
+				Domain:   "example-token.auth0.com",
+				APIToken: jwtToken,
+			},
+		},
+		{
+			name: "it loads access token from config file when cli_login is set and no access token is found in keyring",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":    "example-token.auth0.com",
+				"cli_login": true,
+			},
+			keyringAccessToken:  "",
+			configAccessToken:   jwtToken,
+			expectedDiagnostics: nil,
+			expectedConfig: config.ProviderConfig{
+				Domain:   "example-token.auth0.com",
+				APIToken: jwtToken,
+			},
+		},
+		{
+			name: "it return an error when cli_login is set and no access token is found in keyring and config file",
+			givenTerraformConfig: map[string]interface{}{
+				"domain":    "example.auth0.com",
+				"cli_login": true,
+			},
+			expectedDiagnostics: diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Authentication required",
+				Detail:   "No CLI token found. Please log in using 'auth0 login' via auth0-cli or disable 'AUTH0_CLI_LOGIN'.",
+			}},
+			expectedConfig: config.ProviderConfig{},
+		},
+		{
+			name:                 "it returns an error when it can't initialize the api client",
+			givenTerraformConfig: map[string]interface{}{},
+			expectedDiagnostics: diag.Diagnostics{
+				diag.Diagnostic{
+					Severity: diag.Error,
+					Summary:  "Missing environment variables",
+					Detail: "AUTH0_DOMAIN is required. Then, configure either AUTH0_API_TOKEN, " +
+						"or AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET, " +
+						"or AUTH0_CLIENT_ID, AUTH0_CLIENT_ASSERTION_PRIVATE_KEY, and AUTH0_CLIENT_ASSERTION_SIGNING_ALG, " +
+						"or enable CLI login with AUTH0_CLI_LOGIN=true.",
+				},
+			},
+			expectedConfig: config.ProviderConfig{},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			keyring.MockInit()
+			if testCase.keyringAccessToken != "" {
+				_ = keyring.Set("Auth0 CLI Access Token 0", "example-token.auth0.com", testCase.keyringAccessToken)
+			}
+
+			resourceData := schema.TestResourceDataRaw(t, provider.New().Schema, testCase.givenTerraformConfig)
+			cfg, diags := config.ParseResourceConfigData(resourceData)
+
+			if testCase.expectedDiagnostics != nil {
+				assert.Equal(t, diags, testCase.expectedDiagnostics)
+				return
+			}
+
+			assert.Nil(t, diags)
+			assert.Equal(t, testCase.expectedConfig, cfg)
+		})
+	}
+}
+
+func createJwtToken(t *testing.T) string {
+	token, err := jwt.NewBuilder().
+		Expiration(time.Now().Add(5 * time.Minute)).
+		Build()
+	assert.NoError(t, err)
+
+	signed, err := jwt.Sign(token, jwt.WithInsecureNoSignature())
+	assert.NoError(t, err)
+
+	jwtToken := string(signed)
+	return jwtToken
+}
+
+func createCliConfigFile(t *testing.T, token string) *os.File {
+	f, err := os.CreateTemp("", "auth0-terraform-provider-test")
+	assert.NoError(t, err)
+
+	json := `
+{
+  "tenants": {
+    "example-token.auth0.com": {
+      "name": "example-token",
+      "domain": "example-token.auth0.com",
+      "scopes": [],
+      "expires_at": "2025-10-09T19:06:30.45293+02:00",
+      "access_token": "` + token + `"
+    }
+  }
+}
+`
+
+	_, err = f.Write([]byte(json))
+	assert.NoError(t, err)
+
+	return f
+}