Authentication flow
Overview
The CLI provides a reusable authentication mechanism that you can use with any command requiring valid DataRobot credentials. Cobra's PreRunE hooks handle authentication and ensure credentials are valid before a command executes.
Use authentication in commands
PreRunE hook
Use auth.EnsureAuthenticatedE as your command's PreRunE hook. It already has the
func(*cobra.Command, []string) error shape Cobra expects, so assign it directly.
import "github.com/datarobot/cli/internal/auth"
var MyCmd = &cobra.Command{
Use: "mycommand",
Short: "My command description",
PreRunE: auth.EnsureAuthenticatedE,
Run: func(_ *cobra.Command, _ []string) {
// Command implementation
// Authentication is guaranteed to be valid here
},
}
How it works
The hook functions are outlined below.
- Checks environment credentials first: A complete
DATAROBOT_ENDPOINT(orDATAROBOT_API_ENDPOINT) andDATAROBOT_API_TOKENpair takes precedence over the config file. If the pair fails verification, the command fails with the reason (timeout, malformed endpoint, unreachable endpoint, a non-2xx status from the instance, or an invalid token; only a 401 or 403 blames the token). It never falls back to the stored profile and never starts the login flow, because that would silently run the command against a different DataRobot instance than the one requested. - Checks for valid credentials: With no complete environment pair, checks if a valid API key already exists in the config file.
- Auto-configures URL if missing: If no DataRobot URL is configured, prompts you to set it up.
- Retrieves new credentials: If the stored credentials are missing, or DataRobot rejected them with a 401 or 403, the hook automatically triggers the browser-based login flow. A timeout, an unreachable host, or any other status means DataRobot never judged the credentials, so the login flow does not start and the stored token is left intact.
- Fails early: If authentication cannot be established, the command will not run and returns an error. Credential failures are explained on stderr, so
--output-format jsonleaves stdout empty when the gate rejects your credentials. The interactive paths still use stdout: the browser login flow and the URL prompt.
Direct call for non-command code
For code that isn't a Cobra command, call auth.EnsureAuthenticated(ctx) directly.
import "github.com/datarobot/cli/internal/auth"
func MyFunction(ctx context.Context) error {
// Ensure valid authentication before proceeding.
if !auth.EnsureAuthenticated(ctx) {
return errors.New("authentication failed")
}
// Continue with authenticated operations.
apiKey, err := config.GetAPIKey(ctx)
if err != nil {
return err
}
// ... use apiKey for API calls
return nil
}
When to use a direct call
Use a direct call to add authentication to any command that does the following:
- Makes API calls to DataRobot endpoints.
- Needs to populate DataRobot credentials in configuration files.
- Requires valid authentication to function correctly.
Commands with authentication
The following commands use PreRunE to ensure authentication:
dr dotenv update: Automatically ensures authentication before updating environment variables.dr templates list: Requires authentication to fetch templates from the API.dr templates setup: Requires authentication to fetch template details.
Skip authentication
For advanced use cases where authentication is handled externally or not required, you can bypass authentication checks using the --skip-auth global flag.
Use the --skip-auth global flag to skip authentication for any command. You can also skip authentication with an environment variable.
# Skip authentication for any command
dr templates list --skip-auth
dr dotenv update --skip-auth
# Skip authentication with environment variable
DATAROBOT_CLI_SKIP_AUTH=true dr templates setup
Behavior
When --skip-auth is enabled, it expect the following behavior:
- Bypass all authentication checks: The
EnsureAuthenticated()function returnstrueimmediately without validating credentials. - Emit a warning: Logs a warning message:
Authentication checks are disabled via --skip-auth flag. This may cause API calls to fail. - May cause API failures: Commands that make API calls will likely fail if no valid credentials are present.
When to use skip-auth
The --skip-auth flag is intended for advanced scenarios such as:
- Testing: Test command logic without requiring valid credentials.
- CI/CD pipelines: Use when authentication is managed through environment variables (
DATAROBOT_API_TOKEN). - Offline development: When working in environments without internet access or access to DataRobot.
- Debugging: Isolate authentication issues from other command behavior.
[!WARNING] The
--skip-authflag should only be used when you understand the implications. Most users should rely on the standard authentication flow viadr auth login.
Manual login
You can still run dr auth login to refresh credentials or change accounts. Both that
command and the implicit login inside EnsureAuthenticated go through
auth.RunBrowserLogin, so the two present identically.
Browser login internals
auth.BrowserFlow (internal/auth/browserflow.go) owns the callback listener and splits
the login into three steps, which is what lets the blocking CLI path and the bubbletea
setup wizard share one implementation:
flow, err := auth.NewBrowserFlow(host) // binds localhost:51164
defer flow.Close()
openErr := flow.OpenBrowser() // best effort; report the link if non-nil
key, err := flow.Wait(ctx) // serves until the callback arrives
Two rules matter when changing this code:
- Bind before opening the browser.
NewBrowserFlowbinds the listener so a fast redirect cannot arrive before anything is listening. - Never claim the browser opened without checking.
open.Openreturns an error; pass it throughauth.BrowserStateForand letauth.RenderBrowserPromptchoose the wording. Telling users to watch for a browser that never opened is the bug CFX-6318 fixed.
auth.RunBrowserLoginWith accepts LoginOptions{NoBrowser: true} for --no-browser,
which renders the link prominently without reporting a failure.
Internal APIs
The auth package writes configuration through the allowlisted writer in
internal/config (config.UpdateConfigFile). It does not call
viper.WriteConfig() directly, because that would serialize every key in
viper.AllSettings()—including transient flags such as --yes—
into drconfig.yaml. Outside internal/config/..., all viper access
goes through the internal/config/viperx wrapper. See
Configuration for the full contract.
drconfig.yaml holds the API token in plaintext, so config.UpdateConfigFile writes it
with mode 0600 and chmods it on every write. The chmod is not redundant:
os.WriteFile only applies its perm argument when it creates the file, so a config left
at 0644 by an older CLI would otherwise stay world-readable forever.
WriteConfigFileSilent()—writes only allowlisted keys (config.PersistableKeys) back todrconfig.yamland returns an error.WriteConfigFile()—writes the config file, prints a success message, and returns an error.SetURLAction()—asks for a DataRobot URL and returns whether it changed. On an interactive terminal it runs thetui/hostpickerlist; otherwise it falls back to a plain-text stdin prompt so piped input and the expect-based smoke tests keep working.config.SkipAuthKey—the viper key for--skip-auth. Use the constant at every bind and read site: viper does not treat-and_as equivalent for lookups, and reading"skip_auth"silently ignored the bound flag.
When attaching credentials to an absolute URL returned by a server response,
first check it with drapi.URLMatchesConfiguredBase(). URLs built locally with
config.GetEndpointURL() already stay on the configured DataRobot host.