Skip to content

sasjs auth login - support programmatic (non-interactive) auth #1462

Description

@allanbowe

sasjs auth login is interactive-only — support programmatic (non-interactive) auth for CI / agents

Summary

The sasjs auth login command (the password-grant flow, added in #1460) is interactive-only: it always prompts for username and password via prompts/getString, with no flag, env var, or stdin path to supply them programmatically. This blocks sasjs auth login from being used in CI pipelines, headless agents, and any non-TTY context, which is precisely where automated deploys run.

The workaround (minting the token directly with curl against /SASLogon/oauth/token and writing it to .env.<target> by hand) works but reimplements getTokensWithPasswordGrant outside the CLI, bypasses the CLI's token-verification (/identities/users/@currentUser) and persistence (saveTokens), and is exactly the kind of hand-rolled SAS-auth the CLI exists to eliminate.

Environment

  • @sasjs/cli v4.19.0 (released; includes auth login)
  • Node v24.15.0, Linux (headless agent / CI runner, no TTY)
  • SAS Viya 4, password grant enabled for the sas.cli client

Current behaviour

src/commands/auth/login.ts always does:

const user = await getString(
  'Please enter your SAS username',
  (v) => !!v || 'Username is required.'
)

const { pass } = await prompts(
  { type: 'password', name: 'pass', message: 'Please enter your SAS password' },
  { onCancel: () => { throw new Error('Input cancelled.') } }
)

const { access_token, refresh_token } = await getTokensWithPasswordGrant(target, user, pass)

There is no way to pass user/pass without an interactive TTY:

  • No --username / --password flags (and the password is intentionally a prompts('password') masked input — there's deliberately no plain-arg path for it).
  • No env var (SAS_USERNAME/SAS_PASSWORD or similar) — process.env is never consulted for credentials.
  • Piping stdin (printf 'user\npass\n' | sasjs auth login -t viya) does not work: the prompts library renders a TTY UI that echoes the input back to the terminal (the password appears in the log as the prompt's echo buffer), and the masked input doesn't consume the piped bytes cleanly.

Expected behaviour

At least one (ideally several) of:

  1. Env vars: read SAS_USERNAME (and SAS_PASSWORD) when set, skipping the corresponding prompt entirely. This is the lowest-friction CI pattern — secrets injected via the runner's secret store, never on the command line, never in logs.
  2. Flags: --username / --password (with the usual warning that --password leaks via shell history / process list; prefer env vars). A --password-stdin flag (read the password from stdin, like docker login) is the safest flag form.
  3. Stdin: if stdin is not a TTY and a password is piped, consume it without rendering the masked prompt. (Likely falls out of (1)/(2) naturally.)

In all cases the credentials should flow into the existing getTokensWithPasswordGrantsaveTokens path (no change to token minting, verification, or persistence), and the password must not be logged or persisted (same guarantee as the interactive flow today).

Suggested implementation

In src/commands/auth/login.ts, resolve credentials with a precedence like:

const user =
  process.env.SAS_USERNAME ||
  (await getString('Please enter your SAS username', (v) => !!v || 'Username is required.'))

const pass =
  process.env.SAS_PASSWORD ||
  (await prompts(
    { type: 'password', name: 'pass', message: 'Please enter your SAS password' },
    { onCancel: () => { throw new Error('Input cancelled.') } }
  )).pass

i.e. env var short-circuits the prompt; if neither env var nor TTY is available, fail with a clear message pointing at the env vars. Document the env vars in docs/auth.md and the --help output.

Workaround (today)

Mint the token directly with curl (password grant against the secret-less sas.cli client) and write it to .env.<target> in the format the CLI expects — then every subsequent sasjs command picks it up (and silently refreshes via the stored refresh token):

curl -s -u "sas.cli:" <server>/SASLogon/oauth/token \
  -d "grant_type=password" \
  --data-urlencode "username=$VIYA_USER" \
  --data-urlencode "password=$VIYA_PASS" \
| jq -r '"ACCESS_TOKEN=\(.access_token)\nREFRESH_TOKEN=\(.refresh_token)\nCLIENT=sas.cli\nSECRET="' \
> .env.viya

This is functional but reimplements getTokensWithPasswordGrant by hand, skips the CLI's /identities/users/@currentUser verification, and means the "always use the adapter/CLI to talk to SAS" principle has a carve-out just for minting the token. First-class env-var support would remove it.

Why this matters

  • CI/CD: deploy pipelines can't run auth login interactively. Today they must either use a registered client/secret (sasjs auth, the authorisation-code flow — also interactive, needs a browser) or the curl workaround above. Password-grant with env vars is the natural CI pattern for estates without a registered client.
  • Agents / headless tooling: coding agents and other automation driving sasjs have no TTY. The interactive prompt is the blocker.
  • Consistency: getTokensWithPasswordGrant is already a clean, exported utility — only the command layer is interactive. Surfacing env-var/flag input is a small, contained change that unlocks the whole non-interactive use case.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions