Repository: dbt-labs/dbt-core

Issue:
Indefinite Snowflake auth loop  when using browser authentication
### Describe the bug
We encounter indefinite auth loop while running dbt with browser auth enabled

### Steps To Reproduce
Install dbt 0.17.2 and run anything on Snowflake

### Expected behavior
Expected two auth requests in case of MFA and dbt proceeding to transform.
(non-MFA logins fail into the loop too)

### Screenshots and log output
```
(dbt-transformations) <wiped out>@<wiped out> dbt-transformations % dbt run --models <wiped out>
Running with dbt=0.17.2
Found 129 models, 135 tests, 3 snapshots, 0 analyses, 140 macros, 0 operations, 1 seed file, 53 sources

Initiating login request with your identity provider. A browser window should have opened for you to complete the login. If you can't see it, check existing browser windows, or your OS settings. Press CTRL+C to abort and try again...
Initiating login request with your identity provider. A browser window should have opened for you to complete the login. If you can't see it, check existing browser windows, or your OS settings. Press CTRL+C to abort and try again...
Initiating login request with your identity provider. A browser window should have opened for you to complete the login. If you can't see it, check existing browser windows, or your OS settings. Press CTRL+C to abort and try again...
Initiating login request with your identity provider. A browser window should have opened for you to complete the login. If you can't see it, check existing browser windows, or your OS settings. Press CTRL+C to abort and try again...
Initiating login request with your identity provider. A browser window should have opened for you to complete the login. If you can't see it, check existing browser windows, or your OS settings. Press CTRL+C to abort and try again...
^C^C^Cctrl-c

```

### System information
**Which database are you using dbt with?**
- [ ] postgres
- [ ] redshift
- [ ] bigquery
- [x] snowflake
- [ ] other (specify: ____________)


**The output of `dbt --version`:**
```
installed version: 0.17.2
   latest version: 0.17.2

Up to date!

Plugins:
  - bigquery: 0.17.2
  - snowflake: 0.17.2
  - redshift: 0.17.2
  - postgres: 0.17.2
```

**The operating system you're using:**
MacOS 10.15.5 (19F101)

**The output of `python --version`:**
Python 3.7.7

### Additional context
reverting dbt to 0.17.1 fixed the issue


Discussion:
Hey @eugene-nikolaev, thanks for opening this issue.

Which MFA mechanism are you using for your Snowflake account? dbt connects to Snowflake via its python connector, which [uses Duo Push by default](https://docs.snowflake.com/en/user-guide/security-mfa.html#using-mfa-with-python). That's how I've done dbt CLI + Snowflake + MFA in the past.

It's helpful to know that reverting to v0.17.1 fixes the issue. We made two relevant changes in v0.17.2:
* #2635: added an env var (`DBT_SNOWFLAKE_CONNECTOR_DEBUG_LOGGING`) to turn on debug-level logging from Snowflake's python connector
* #2650: close open Snowflake connections after using them

I'm guessing that the latter is at issue, and the former may help us troubleshoot what's going on.
@jtcohen6 thanks for reply.
Yes it is Duo Mobile for MFA.
However it isn't related to MFA in any way because other developer has the same without MFA.
It's definitely going to be the latter. Fixing this is going to be a bit involved, unfortunately.

Instead of closing everything we'll have to go back to the old way of not calling `close` when the connection is open. Then we'll have to do something clever to make sure `close` gets called so we don't deadlock with the keepalive thread again. I'm not sure what that means happens to MFA if a model hits a connection-closing error on 0.17.1. Do we make you re-auth?
@eugene-nikolaev Got it, I think your best bet for now is to use v0.17.1

@beckjake Let's pull this into v0.18.0, if possible
@jtcohen6, @beckjake, got it, thanks! Will wait.
I think instead of reverting that change (which is still on the table, though I would rather avoid it) we may be able to fix this by implementing #2613. It appears to me that on Linux, the token cache is global to the executing user (in `~/.cache/snowflake/` by default). I think that would solve these issues pretty nicely, though of course you'd have to enable the token cache on the server. I'm not super clear on the mac/windows flow, it looks like it uses the os' "keyring" mechanism, whatever that is.

Is that a reasonable approach?


Resolving patch:
diff --git a/core/setup.py b/core/setup.py
--- a/core/setup.py
+++ b/core/setup.py
@@ -68,9 +68,9 @@ def read(fname):
         'logbook>=1.5,<1.6',
         'typing-extensions>=3.7.4,<3.8',
         # the following are all to match snowflake-connector-python
-        'requests>=2.18.0,<2.23.0',
-        'idna<2.9',
-        'cffi>=1.9,<1.14',
+        'requests>=2.18.0,<2.24.0',
+        'idna<2.10',
+        'cffi>=1.9,<1.15',
     ],
     zip_safe=False,
     classifiers=[
diff --git a/plugins/snowflake/dbt/adapters/snowflake/connections.py b/plugins/snowflake/dbt/adapters/snowflake/connections.py
--- a/plugins/snowflake/dbt/adapters/snowflake/connections.py
+++ b/plugins/snowflake/dbt/adapters/snowflake/connections.py
@@ -88,6 +88,8 @@ def auth_args(self):
                     )
 
                 result['token'] = token
+            # enable the token cache
+            result['client_store_temporary_credential'] = True
         result['private_key'] = self._get_private_key()
         return result
 
diff --git a/plugins/snowflake/setup.py b/plugins/snowflake/setup.py
--- a/plugins/snowflake/setup.py
+++ b/plugins/snowflake/setup.py
@@ -41,9 +41,9 @@
     },
     install_requires=[
         'dbt-core=={}'.format(package_version),
-        'snowflake-connector-python==2.2.1',
+        'snowflake-connector-python==2.2.10',
         'azure-common<2.0.0',
-        'azure-storage-blob<12.0.0',
+        'azure-storage-blob>=12.0.0,<13.0.0',
         'urllib3>=1.20,<1.26.0',
         # this seems sufficiently broad
         'cryptography>=2,<3',


Test patch:
diff --git a/test/unit/test_snowflake_adapter.py b/test/unit/test_snowflake_adapter.py
--- a/test/unit/test_snowflake_adapter.py
+++ b/test/unit/test_snowflake_adapter.py
@@ -302,7 +302,7 @@ def test_authenticator_user_pass_authentication(self):
                 password='test_password', role=None, schema='public',
                 user='test_user', warehouse='test_warehouse',
                 authenticator='test_sso_url', private_key=None,
-                application='dbt')
+                application='dbt', client_store_temporary_credential=True)
         ])
 
     def test_authenticator_externalbrowser_authentication(self):
@@ -320,7 +320,7 @@ def test_authenticator_externalbrowser_authentication(self):
                 client_session_keep_alive=False, database='test_database',
                 role=None, schema='public', user='test_user',
                 warehouse='test_warehouse', authenticator='externalbrowser',
-                private_key=None, application='dbt')
+                private_key=None, application='dbt', client_store_temporary_credential=True)
         ])
 
     def test_authenticator_oauth_authentication(self):
@@ -339,7 +339,7 @@ def test_authenticator_oauth_authentication(self):
                 client_session_keep_alive=False, database='test_database',
                 role=None, schema='public', user='test_user',
                 warehouse='test_warehouse', authenticator='oauth', token='my-oauth-token',
-                private_key=None, application='dbt')
+                private_key=None, application='dbt', client_store_temporary_credential=True)
         ])
 
     @mock.patch('dbt.adapters.snowflake.SnowflakeCredentials._get_private_key', return_value='test_key')