Skip to content
>GLB_
Go back

Snowflake SSO from the terminal: CLI, dbt Core and DBeaver

One SSO login, cached for 4 hours, shared by the Snowflake CLI, dbt Core and DBeaver. It took me a full afternoon of errors to get there. Here is the short version.

Our Snowflake account authenticates through Microsoft Entra ID (Azure AD) SSO. Snowsight in the browser just works. The terminal is a different story: SAML errors, a browser tab on every single command, a Python version dbt refuses to run on, and a corporate proxy breaking every download.

TL;DR

ToolWhat made it work
Snowflake CLIauthenticator = "externalbrowser" in config.toml
Browser on every commandclient_store_temporary_credential = true + account parameter ALLOW_ID_TOKEN = TRUE
dbt CorePython 3.12 venv via uv, a personal profiles.yml outside the repo, --no-version-check
DBeaverJDBC driver property authenticator=externalbrowser
Corporate TLSUse the Windows certificate store (UV_NATIVE_TLS, truststore, Windows-ROOT)

Step 1: the first wall, AADSTS650056

The first snow connection test opened the browser, landed on Microsoft MyApps, and died with:

AADSTS650056: Misconfigured application.
SAML Issuer / ACS mismatch

Nothing on my laptop could fix this. The error comes from the Snowflake Enterprise App in Entra ID: its Identifier (Entity ID) and Reply URL (ACS) must match the account URL the client actually calls.

The catch is that one Snowflake account has two URLs:

If the SAML app is registered with one and your client uses the other, SSO fails. What I did:

  1. Opened a ticket with IT, with the exact error code and a 3-step repro (run CLI → MyApps opens → AADSTS650056).
  2. Used the org-account format in every client from then on. It is the one that worked once IT fixed the app.

// Lesson: put the error code and the repro in the ticket. “SSO doesn’t work” sits in a queue; “AADSTS650056 on externalbrowser from the CLI” gets routed to the identity team.

Step 2: Snowflake CLI with externalbrowser

First gotcha: on Windows the config file is not in ~/.snowflake. Ask the CLI where it reads from:

$ snow --info
# look for: default_config_file_path
# usually: C:\Users\<you>\AppData\Local\snowflake\config.toml

A .snowflake folder in your home directory, or the SNOWFLAKE_HOME variable, overrides that default. I spent ten minutes editing the wrong file before checking.

The connection block:

[connections.dev]
account       = "<org>-<account>"
user          = "you@company.com"
authenticator = "externalbrowser"
role          = "<YOUR_DEV_ROLE>"
warehouse     = "<YOUR_DEV_WH>"
database      = "<YOUR_DEV_DB>"

Test it, then run a query:

$ snow connection test -c dev
$ snow sql -c dev -q "select current_user(), current_role(), current_warehouse();"
$ snow sql -c dev -f .\query.sql     # from a file
$ snow sql -c dev                    # interactive REPL, one login per session

It worked. And it opened a browser tab on every single command.

Step 3: stop the browser opening on every command

Snowflake can cache an SSO ID token in the OS credential store (Windows Credential Manager, macOS Keychain) and reuse it. It needs two switches, one on each side.

Client side, add one line to the connection:

client_store_temporary_credential = true

Account side, the token is only issued if the account allows it. Check it yourself, no admin needed:

$ snow sql -c dev -q "show parameters like 'ALLOW_ID_TOKEN' in account;"

Mine came back false, the default. Only an ACCOUNTADMIN can change it, so I asked IT to run:

ALTER ACCOUNT SET ALLOW_ID_TOKEN = TRUE;

The argument that got it approved in one message: this only caches the token locally for a few hours. Every user still authenticates through Entra ID and MFA.

Once the parameter showed true at level ACCOUNT, the next login stored the token and the second command ran without a browser.

What to know about the token:

Step 4: dbt Core locally with the same SSO

This is where most of the afternoon went. Four traps, in the order I hit them.

Trap 1: Python is too new. My machine only had Python 3.14. dbt-core 1.10 supports 3.9 to 3.13, so pip quietly resolved a dbt-core beta instead, which then tried to download an extra wheel from GitHub and failed on TLS. Two problems, one root cause.

You cannot create a venv with a Python version you do not have installed. uv can: it downloads the interpreter and builds the venv in one step, no admin rights.

$ py -m pip install --user uv
$ $env:UV_NATIVE_TLS = "1"            # use the Windows cert store (corporate proxy)
$ py -m uv venv --python 3.12 .venv
$ .\.venv\Scripts\Activate.ps1
$ python --version                     # 3.12.x

Trap 2: uv installs into the wrong Python. py -m uv pip install ... runs uv from the system Python, and uv targets that interpreter, not your activated venv. Mine failed with Access is denied on C:\Program Files\Python314. Point it at the venv explicitly, and pin versions to what your deploy uses:

$ py -m uv pip install --python .\.venv\Scripts\python.exe `
    "dbt-core~=1.10.15" "dbt-snowflake~=1.10.0" `
    "snowflake-connector-python[secure-local-storage]<5.0.0" truststore

Ignore the “update available” and “deprecated” banners. Local should match the version that runs in CI, not the newest one.

Trap 3: the repo’s profiles.yml is not for you. dbt looks for profiles.yml in the project folder before ~/.dbt. Ours exists for Snowflake-native dbt (Workspaces), where the session supplies the account, so locally it failed with 'account' is a required property. Do not edit the shared one. Create your own and point dbt at it:

# C:\Users\<you>\.dbt\profiles.yml
my_project:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: <org>-<account>
      user: you@company.com
      authenticator: externalbrowser
      role: <YOUR_DEV_ROLE>
      warehouse: <YOUR_DEV_WH>
      database: <YOUR_DEV_DB>
      schema: <YOUR_SCHEMA>
      threads: 4
$ [Environment]::SetEnvironmentVariable("DBT_PROFILES_DIR", "$env:USERPROFILE\.dbt", "User")
$ $env:DBT_PROFILES_DIR = "$env:USERPROFILE\.dbt"
$ dbt debug --target dev               # All checks passed!

dbt debug did not open the browser: dbt-snowflake reused the token the CLI had already cached.

Trap 4: dbt deps crashes on a version string. dbt 1.10 walks the metadata of every published version of a package on the hub. A newer release declared require-dbt-version as ">=1.10.5,<3.0.0", which the 1.10 semver parser rejects:

SemverError: ">=1.10.5,<3.0.0" is not a valid semantic version.

Skipping the compatibility check makes dbt resolve the versions pinned in packages.yml directly:

$ dbt deps --no-version-check
$ [Environment]::SetEnvironmentVariable("DBT_VERSION_CHECK", "false", "User")

Then check git diff package-lock.yml before committing anything: the resolver may have picked newer minor versions than CI uses.

After that: dbt compile, dbt show --limit 10, dbt run --select ... all worked from my laptop.

Step 5: DBeaver with the same login

DBeaver does not read config.toml or profiles.yml. You type the same values once.

New Database Connection → Snowflake, tab Main:

FieldValue
Host<org>-<account>.snowflakecomputing.com
Port443
Database / Warehouse / Roleyour dev values
Useryou@company.com
Passwordempty, “Save password” unchecked

Driver properties (add them if they are not listed):

PropertyValueWhy
authenticatorexternalbrowserSSO through the browser, like the CLI
CLIENT_STORE_TEMPORARY_CREDENTIALtrueSame 4-hour token cache
QUERY_TAGdbeaver_<you>Find your own queries in query_history

On the first Test Connection, DBeaver downloads the Snowflake JDBC driver. Behind a TLS-inspecting proxy that download fails, because Java ships its own trust store. Tell the JVM to use Windows’ instead, by adding this after -vmargs in dbeaver.ini and restarting:

-Djavax.net.ssl.trustStoreType=Windows-ROOT

Plan B: download snowflake-jdbc-<version>.jar from Maven Central in the browser and add it under Driver Manager → Snowflake → Libraries.

The lesson I did not expect: local dbt is not local compute

My motivation for all this was cost: run dbt on my laptop instead of burning warehouse credits. Then the first dbt run finished and I realized the models had been built by the warehouse anyway.

dbt is a compiler, not an engine. On your machine it reads the .sql files, resolves Jinja (ref, source, macros) and generates the final SQL. Then it sends that SQL to Snowflake, and Snowflake reads, transforms and writes. The rows never touch your laptop.

laptop (dbt)                         Snowflake (warehouse)
-------------                        ---------------------
parse + Jinja -> SQL  ---- send ---> read RAW, transform, write table
"OK"                  <-- result --- (this is what you pay for)

So what does local dbt actually buy you?

Free on your CPUStill billed by the warehouse
dbt parse, compile, ls, deps, docs generatedbt run, build, test, show
Impact analysis: dbt ls --select my_model+dbt build --empty (LIMIT 0, nearly free but not zero)
VS Code, git, linters, target/compiled for debuggingEvery query from the CLI or DBeaver

Running truly locally means swapping both the engine and the data (DuckDB, an emulator like fakesnow). With real data that means copying it out of Snowflake first, which also costs warehouse time. If the data is sensitive, it also means a conversation with Security before it lands on a laptop.

Where the money actually went in my case: a dev run had about 8 seconds of real compute, and the warehouse stayed up for the default 5-minute AUTO_SUSPEND afterwards. The cheapest fix was not local dbt. It was this:

ALTER WAREHOUSE <YOUR_DEV_WH> SET AUTO_SUSPEND = 60;

Plus three habits: validate with compile and --empty before run, select single models instead of folders, and batch commands instead of spreading them over the afternoon.

Cheat sheet

WhereSettingValue
Snowflake account (admin)ALLOW_ID_TOKENTRUE
Snowflake CLI config.tomlauthenticator"externalbrowser"
Snowflake CLI config.tomlclient_store_temporary_credentialtrue
All clientsaccount<org>-<account> (org-account format)
Pythonvenv version3.12 via uv venv --python 3.12
uvUV_NATIVE_TLS1
dbtDBT_PROFILES_DIR%USERPROFILE%\.dbt
dbtDBT_VERSION_CHECKfalse
dbt profiles.ymlauthenticatorexternalbrowser
DBeaver driver propertiesauthenticator / CLIENT_STORE_TEMPORARY_CREDENTIALexternalbrowser / true
dbeaver.ini-Djavax.net.ssl.trustStoreTypeWindows-ROOT
Dev warehouse (admin)AUTO_SUSPEND60

> one login, three tools, 4 hours of peace_

Sources


Share this post:

Related Posts


Previous Post
Liquidity pools vs lending protocols: what you're actually selling
Next Post
Connecting Clinical and Operational Data to Tuva with dbt