Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,39 @@ There are two ways to authenticate:

- **As a user** - Recommended when invoking on a personal machine or other interactive environment. Facilitated by [device authorization](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow) flow and cannot be used for private cloud tenants.
- **As a machine** - Recommended when running on a server or non-interactive environments (ex: CI, authenticating to a **private cloud**). Facilitated by [client credentials](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) flow. Flags available for bypassing interactive shell.
- **Profile-based login:** Use `--profile` to easily switch between multiple Auth0 tenants or environments (e.g., `default`, `dev`, etc.)

---

### Configure Your Credentials

Create a credentials file at `~/.auth0/credentials` (or set the `AUTH0_CREDENTIALS_FILE` environment variable to use a custom path).

The file should use **INI format** and can contain multiple profiles.
**Example:**

```ini
[default]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better if profiles can use common variable across profile

example:

[common]
tenant = your-tenant.auth0.com

[dev-profile-1]
client_id = YOUR_CLIENT_ID_1
client_secret = YOUR_CLIENT_SECRET_1

[dev-profile-2]
client_id = YOUR_DEV_CLIENT_ID_2
client_secret = YOUR_DEV_CLIENT_SECRET_2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • show the list of current profiles
--profile ls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. In most cases, we log in using different tenants, and each profile can correspond to a different tenant. So, I don't see much value in having a common one.

  2. Profiles are manually stored locally by the user, so I see less benefit in supporting a list command just to view what they’ve explicitly added to their local file.
    Besides, we already have the tenants ls command to view currently logged-in tenants.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Using common variable is a very standard and useful pattern example, .gitconfig (core)
  • --profile ls is or --profile <profile-name> will improve cli experience.

domain = your-tenant.auth0.com
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET

[dev]
domain = dev-tenant.auth0.com
client_id = YOUR_DEV_CLIENT_ID
client_secret = YOUR_DEV_CLIENT_SECRET
```

- Each section name (e.g., `[default]`, `[dev]`) is a **tenant-profile**.
- You can add as many tenant profiles as needed for different tenants/environments.

---

## Environment Variables

- `AUTH0_CREDENTIALS_FILE`: Path to the credentials file (defaults to `~/.auth0/credentials`).

---

> **Warning**
> Authenticating as a user is not supported for **private cloud** tenants.
Expand Down
2 changes: 2 additions & 0 deletions docs/auth0_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ auth0 login [flags]
auth0 login
auth0 login --domain <tenant-domain> --client-id <client-id> --client-secret <client-secret>
auth0 login --scopes "read:client_grants,create:client_grants"
auth0 login --profile <tenant-profile>
```


Expand All @@ -31,6 +32,7 @@ auth0 login [flags]
--client-id string Client ID of the application when authenticating via client credentials.
--client-secret string Client secret of the application when authenticating via client credentials.
--domain string Tenant domain of the application when authenticating via client credentials.
--profile string Tenant Profile Label name to load Auth0 credentials from. If not provided, the default profile will be used.
--scopes strings Additional scopes to request when authenticating via device code flow. By default, only scopes for first-class functions are requested. Primarily useful when using the api command to execute arbitrary Management API requests.
```

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
golang.org/x/text v0.25.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM=
gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
88 changes: 76 additions & 12 deletions internal/cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"

"gopkg.in/ini.v1"

"github.com/pkg/browser"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -48,21 +52,61 @@ var (
IsRequired: false,
AlwaysPrompt: false,
}

loginTenantProfileLabel = Flag{
Name: "Tenant Profile Label",
LongForm: "profile",
Help: "Tenant Profile Label name to load Auth0 credentials from. If not provided, the default profile will be used.",
IsRequired: false,
AlwaysPrompt: false,
}
)

type LoginInputs struct {
Domain string
ClientID string
ClientSecret string
AdditionalScopes []string
TenantProfile string
}

func (i *LoginInputs) isLoggingInWithAdditionalScopes() bool {
return len(i.AdditionalScopes) > 0
}

func loadAuth0Credentials(profile string, inputs *LoginInputs) error {
credPath := os.Getenv("AUTH0_CREDENTIALS_FILE")
if credPath == "" {
home, err := os.UserHomeDir()
if err != nil {
return err
}

credPath = filepath.Join(home, ".auth0", "credentials")
}

cfg, err := ini.Load(credPath)
if err != nil {
return err
}

if !cfg.HasSection(profile) {
return fmt.Errorf("profile %q not found", profile)
}

sec := cfg.Section(profile)

inputs.Domain = sec.Key("domain").String()
inputs.ClientID = sec.Key("client_id").String()
inputs.ClientSecret = sec.Key("client_secret").String()

return nil
}

func loginCmd(cli *cli) *cobra.Command {
var inputs LoginInputs
var (
inputs LoginInputs
)

cmd := &cobra.Command{
Use: "login",
Expand All @@ -74,8 +118,16 @@ func loginCmd(cli *cli) *cobra.Command {
"this is the recommended method for Private Cloud users.\n\n",
Example: ` auth0 login
auth0 login --domain <tenant-domain> --client-id <client-id> --client-secret <client-secret>
auth0 login --scopes "read:client_grants,create:client_grants"`,
auth0 login --scopes "read:client_grants,create:client_grants"
auth0 login --profile <tenant-profile>`,
RunE: func(cmd *cobra.Command, args []string) error {
if inputs.TenantProfile != "" {
err := loadAuth0Credentials(inputs.TenantProfile, &inputs)
if err != nil {
return fmt.Errorf("failed to load auth0 credentials from %q: %v", inputs.TenantProfile, err)
}
}

var selectedLoginType string
const loginAsUser, loginAsMachine = "As a user", "As a machine"
shouldLoginAsUser, shouldLoginAsMachine := false, false
Expand Down Expand Up @@ -189,6 +241,7 @@ func loginCmd(cli *cli) *cobra.Command {
loginTenantDomain.RegisterString(cmd, &inputs.Domain, "")
loginClientID.RegisterString(cmd, &inputs.ClientID, "")
loginClientSecret.RegisterString(cmd, &inputs.ClientSecret, "")
loginTenantProfileLabel.RegisterString(cmd, &inputs.TenantProfile, "")
loginAdditionalScopes.RegisterStringSlice(cmd, &inputs.AdditionalScopes, []string{})
cmd.MarkFlagsMutuallyExclusive("client-id", "scopes")
cmd.MarkFlagsMutuallyExclusive("client-secret", "scopes")
Expand Down Expand Up @@ -317,16 +370,11 @@ func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string, do

// RunLoginAsMachine facilitates the authentication process using client credentials (client ID, client secret).
func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *cobra.Command) error {
if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil {
return err
}

if err := loginClientID.Ask(cmd, &inputs.ClientID, nil); err != nil {
return err
}

if err := loginClientSecret.AskPassword(cmd, &inputs.ClientSecret); err != nil {
return err
if inputs.TenantProfile == "" {
err := promptForCredentials(cmd, &inputs)
if err != nil {
return err
}
}

token, err := auth.GetAccessTokenFromClientCreds(
Expand Down Expand Up @@ -371,3 +419,19 @@ func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *c

return nil
}

func promptForCredentials(cmd *cobra.Command, inputs *LoginInputs) error {
if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil {
return err
}

if err := loginClientID.Ask(cmd, &inputs.ClientID, nil); err != nil {
return err
}

if err := loginClientSecret.AskPassword(cmd, &inputs.ClientSecret); err != nil {
return err
}

return nil
}
Loading