Repository: snowflakedb/snowflake-connector-net
Pull request #1145: Refactor Authorization Code Flow and External Browser authentications to reduce code duplication
Author: login: sfc-gh-knozderko
Created: 2025-04-15T15:40:26Z | Merged: 2025-04-22T13:16:06Z

### Description
Refactor Authorization Code Flow and External Browser authentications to reduce code duplication

### Checklist
- [x] Code compiles correctly
- [x] Code is formatted according to [Coding Conventions](../blob/master/CodingConventions.md)
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing (`dotnet test`)
- [ ] Extended the README / documentation, if necessary
- [ ] Provide JIRA issue id (if possible) or GitHub issue id in PR name



Diff:
diff --git a/Snowflake.Data.Tests/Mock/MockBrowser.cs b/Snowflake.Data.Tests/Mock/MockBrowser.cs
index 2f44d4aa7..f54ebd4ce 100644
--- a/Snowflake.Data.Tests/Mock/MockBrowser.cs
+++ b/Snowflake.Data.Tests/Mock/MockBrowser.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Net.Http;
 using Snowflake.Data.Core.Authenticator.Browser;
 
@@ -12,9 +13,9 @@ public MockBrowser()
             _httpClient = new HttpClient();
         }
 
-        public void Run(string url)
+        public void Run(Uri uri)
         {
-            _httpClient.GetAsync(url); // we should not wait for the response because in case of 302 Redirect it will be sent to a different endpoint so we won't get a response here
+            _httpClient.GetAsync(uri.ToString()); // we should not wait for the response because in case of 302 Redirect it will be sent to a different endpoint so we won't get a response here
         }
     }
 }
diff --git a/Snowflake.Data.Tests/UnitTests/Authenticator/Browser/WebBrowserStarterTest.cs b/Snowflake.Data.Tests/UnitTests/Authenticator/Browser/WebBrowserStarterTest.cs
index 59f28b8cd..81718c58d 100644
--- a/Snowflake.Data.Tests/UnitTests/Authenticator/Browser/WebBrowserStarterTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Authenticator/Browser/WebBrowserStarterTest.cs
@@ -1,3 +1,4 @@
+using System;
 using NUnit.Framework;
 using Moq;
 using Snowflake.Data.Client;
@@ -15,13 +16,14 @@ public void TestRunUrlInBrowser()
             // arrange
             var runner = new Mock<WebBrowserRunner>();
             var webBrowserStarter = new WebBrowserStarter(runner.Object);
-            var validUrl = "http://localhost:8001/endpoint1";
+            var validUrl = new Url("http://localhost:8001/endpoint1");
+            var uri = new Uri(validUrl.Value);
 
             // act
             webBrowserStarter.StartBrowser(validUrl);
 
             // assert
-            runner.Verify(r => r.Run(validUrl), Times.Once);
+            runner.Verify(r => r.Run(uri), Times.Once);
         }
 
         [Test]
@@ -32,13 +34,14 @@ public void TestValidateUrl(string invalidUrl)
             // arrange
             var runner = new Mock<WebBrowserRunner>();
             var webBrowserStarter = new WebBrowserStarter(runner.Object);
+            var url = new Url(invalidUrl);
 
             // act
-            var thrown = Assert.Throws<SnowflakeDbException>(() => webBrowserStarter.StartBrowser(invalidUrl));
+            var thrown = Assert.Throws<SnowflakeDbException>(() => webBrowserStarter.StartBrowser(url));
 
             // assert
             Assert.AreEqual(SFError.INVALID_BROWSER_URL.GetAttribute<SFErrorAttr>().errorCode, thrown.ErrorCode);
-            runner.Verify(r => r.Run(It.IsAny<string>()), Times.Never);
+            runner.Verify(r => r.Run(It.IsAny<Uri>()), Times.Never);
         }
     }
 }
diff --git a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeAuthenticatorTest.cs b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeAuthenticatorTest.cs
index e2920800e..6e0372116 100644
--- a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeAuthenticatorTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeAuthenticatorTest.cs
@@ -6,6 +6,7 @@
 using Snowflake.Data.Core.Authenticator.Browser;
 using Snowflake.Data.Core.Session;
 using Moq;
+using Snowflake.Data.Client;
 
 namespace Snowflake.Data.Tests.UnitTests.Authenticator
 {
@@ -29,6 +30,7 @@ public class OAuthAuthorizationCodeAuthenticatorTest
         private static readonly string s_defaultAuthorizationEndpoint = $"https://{Host}:{Port}/oauth/authorize";
         private const int RandomPort = 1234;
         private static readonly string s_defaultRedirectUri = $"http://127.0.0.1:{RandomPort}/";
+        private const string CodeVerifier = "codeVerifierForAuthorizationCodeFlowMustBeALongString";
 
         [Test]
         public void TestUseDefaultValues()
@@ -109,6 +111,29 @@ public void TestUseCustomizedValues()
             listenerStarter.Verify(s => s.GetRandomUnusedPort(), Times.Never);
         }
 
+        [Test]
+        public void TestBrowserShouldFailWithExceptionWithMaskedUrl()
+        {
+            // arrange
+            var customizedProperties = $"oauthScope={AuthorizationScope};oauthAuthorizationUrl={ExternalAuthorizationUrl};oauthTokenRequestUrl={ExternalTokenUrl};oauthRedirectUri={CustomRedirectUri};";
+            var browserRunner = new Mock<IWebBrowserRunner>();
+            var listenerStarter = new Mock<WebListenerStarter>();
+            var authenticator = PrepareAuthenticator(browserRunner.Object, listenerStarter.Object, customizedProperties);
+            var authorizationData = authenticator.PrepareAuthorizationData();
+            var url = authorizationData.Request.GetUrl();
+            var invalidUrl = new Url(url.Value.Replace("https://", "ftp://"), url.ValueWithoutSecrets.Replace("https://", "ftp://"));
+            var browserStarter = new WebBrowserStarter(browserRunner.Object);
+            var expectedUrlInException = "ftp://localhost:8888/oauth/authorize?client_id=****&response_type=code&redirect_uri=http%3a%2f%2flocalhost%3a8001%2foauth%2fredirect&scope=session%3arole%3aANALYST&code_challenge=xqXLsNRtro84G9aBLgX4c7djUqmrzbJCrW39HerS5Hk&code_challenge_method=S256&state=****";
+
+            // act
+            var thrown = Assert.Throws<SnowflakeDbException>(() => browserStarter.StartBrowser(invalidUrl));
+
+            // assert
+            Assert.That(thrown.Message, Does.Contain("Invalid browser url"));
+            var urlInException = thrown.Message.Split('\"')[1];
+            Assert.AreEqual(expectedUrlInException, urlInException);
+        }
+
         private OAuthAuthorizationCodeAuthenticator PrepareAuthenticator(
             IWebBrowserRunner browserRunner,
             WebListenerStarter listenerStarter,
@@ -120,6 +145,8 @@ private OAuthAuthorizationCodeAuthenticator PrepareAuthenticator(
             var challengeProvider = new Mock<ChallengeProvider>();
             challengeProvider.Setup(c => c.GenerateState())
                 .Returns(State);
+            challengeProvider.Setup(c => c.GenerateCodeVerifier())
+                .Returns(new CodeVerifier(CodeVerifier));
             var webBrowserMock = new WebBrowserStarter(browserRunner);
             var authenticator = new OAuthAuthorizationCodeAuthenticator(session, challengeProvider.Object, webBrowserMock, listenerStarter);
             session.ReplaceAuthenticator(authenticator);
diff --git a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeFlowTest.cs b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeFlowTest.cs
index ef9b5fd82..9aaf1a864 100644
--- a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeFlowTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthAuthorizationCodeFlowTest.cs
@@ -90,7 +90,6 @@ public async Task TestSuccessfulAuthorizationCodeFlowAsync()
         }
 
         [Test]
-        [Ignore("temporarily ignored until the feature branch is updated with reading tokens fix from master branch")]
         public void TestSuccessfulAuthorizationCodeFlowWithDefaultCache()
         {
             // arrange
@@ -454,6 +453,8 @@ public void TestTokenRequestErrorAsync()
             var challengeProvider = new Mock<ChallengeProvider>();
             challengeProvider.Setup(c => c.GenerateState())
                 .Returns("abc123");
+            challengeProvider.Setup(c => c.GenerateCodeVerifier())
+                .CallBase();
             var webBrowserMock = new WebBrowserStarter(new MockBrowser());
             var authenticator = new OAuthAuthorizationCodeAuthenticator(session, challengeProvider.Object, webBrowserMock, WebListenerStarter.Instance);
             session.ReplaceAuthenticator(authenticator);
diff --git a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthClientCredentialFlowTest.cs b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthClientCredentialFlowTest.cs
index 28bb87db7..29eb9d361 100644
--- a/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthClientCredentialFlowTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/Authenticator/OAuthClientCredentialFlowTest.cs
@@ -81,7 +81,6 @@ public async Task TestSuccessfulClientCredentialsFlowAsync()
         }
 
         [Test]
-        [Ignore("temporarily ignored until the feature branch is updated with reading tokens fix from master branch")]
         public void TestSuccessfulClientCredentialFlowWithDefaultCache()
         {
             // arrange
diff --git a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs
index e503efc02..1841ce468 100644
--- a/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs
+++ b/Snowflake.Data.Tests/UnitTests/SFExternalBrowserTest.cs
@@ -5,13 +5,13 @@
 using Snowflake.Data.Core.Authenticator;
 using Snowflake.Data.Core.CredentialManager;
 using Snowflake.Data.Core.CredentialManager.Infrastructure;
-using Snowflake.Data.Core.Tools;
 using System;
 using System.Net.Http;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Web;
+using Snowflake.Data.Core.Authenticator.Browser;
 using Snowflake.Data.Core.Session;
 
 namespace Snowflake.Data.Tests.UnitTests
@@ -20,21 +20,21 @@ namespace Snowflake.Data.Tests.UnitTests
     class SFExternalBrowserTest
     {
         [ThreadStatic]
-        private static Mock<BrowserOperations> t_browserOperations;
+        private static Mock<IWebBrowserRunner> t_browserRunner;
 
         private static HttpClient s_httpClient = new HttpClient();
 
         [SetUp]
         public void BeforeEach()
         {
-            t_browserOperations = new Mock<BrowserOperations>();
+            t_browserRunner = new Mock<IWebBrowserRunner>();
         }
 
         [Test]
         public void TestDefaultAuthentication()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -44,19 +44,19 @@ public void TestDefaultAuthentication()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             sfSession.Open();
 
             Assert.IsTrue(sfSession._disableConsoleLogin);
-            t_browserOperations.Verify(b => b.OpenUrl(It.Is<Uri>(s => localhostRegex.IsMatch(s.ToString()))), Times.Once());
-            t_browserOperations.VerifyNoOtherCalls();
+            t_browserRunner.Verify(b => b.Run(It.Is<Uri>(s => localhostRegex.IsMatch(s.ToString()))), Times.Once());
+            t_browserRunner.VerifyNoOtherCalls();
         }
 
         [Test]
         public void TestConsoleLogin()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     var port = HttpUtility.ParseQueryString(uri.Query).Get("browser_mode_redirect_port");
                     var browserUrl = $"http://localhost:{port}/?token=mockToken";
@@ -68,11 +68,11 @@ public void TestConsoleLogin()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("disable_console_login=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             sfSession.Open();
             Assert.IsFalse(sfSession._disableConsoleLogin);
-            t_browserOperations.Verify(b => b.OpenUrl(It.Is<Uri>(s => s.ToString().Contains("https://test.snowflakecomputing.com/console/login?"))), Times.Once());
-            t_browserOperations.VerifyNoOtherCalls();
+            t_browserRunner.Verify(b => b.Run(It.Is<Uri>(s => s.ToString().Contains("https://test.snowflakecomputing.com/console/login?"))), Times.Once());
+            t_browserRunner.VerifyNoOtherCalls();
         }
 
         [Test]
@@ -93,14 +93,14 @@ public void TestSSOToken()
             var sfSession = new SFSession($"CLIENT_STORE_TEMPORARY_CREDENTIAL=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
             sfSession.Open();
 
-            t_browserOperations.Verify(b => b.OpenUrl(It.IsAny<Uri>()), Times.Never);
+            t_browserRunner.Verify(b => b.Run(It.IsAny<Uri>()), Times.Never);
         }
 
         [Test]
         public void TestThatTokenIsStoredWhenCacheIsEnabled()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -116,7 +116,7 @@ public void TestThatTokenIsStoredWhenCacheIsEnabled()
                 IdToken = expectedIdToken
             };
             var sfSession = new SFSession($"client_store_temporary_credential=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(string.Empty, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
@@ -128,8 +128,8 @@ public void TestThatTokenIsStoredWhenCacheIsEnabled()
         [Test]
         public void TestThatTokenIsNotStoredWhenCacheIsDisabled()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -145,7 +145,7 @@ public void TestThatTokenIsNotStoredWhenCacheIsDisabled()
                 IdToken = expectedIdToken
             };
             var sfSession = new SFSession($"client_store_temporary_credential=false;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(string.Empty, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
@@ -157,8 +157,8 @@ public void TestThatTokenIsNotStoredWhenCacheIsDisabled()
         [Test]
         public void TestThatRetriesAuthenticationForInvalidIdToken()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -179,13 +179,13 @@ public void TestThatRetriesAuthenticationForInvalidIdToken()
                 ThrowInvalidIdToken = true
             };
             var sfSession = new SFSession($"CLIENT_STORE_TEMPORARY_CREDENTIAL=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SFExternalBrowserTest.SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(invalidIdToken, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
             sfSession.Open();
 
-            t_browserOperations.Verify(b => b.OpenUrl(It.IsAny<Uri>()), Times.Once);
+            t_browserRunner.Verify(b => b.Run(It.IsAny<Uri>()), Times.Once);
             Assert.AreEqual(expectedIdToken, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
         }
 
@@ -214,8 +214,8 @@ public void TestThatDoesNotRetryAuthenticationForNonInvalidIdTokenException()
         [Test]
         public void TestThatThrowsTimeoutErrorWhenNoBrowserResponse()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback(async (Uri uri) => {
                     await Task.Delay(1000).ContinueWith(_ =>
                     {
@@ -228,7 +228,7 @@ await Task.Delay(1000).ContinueWith(_ =>
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession($"CLIENT_STORE_TEMPORARY_CREDENTIAL=false;browser_response_timeout=0;account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             var thrown = Assert.Throws<SnowflakeDbException>(() => sfSession.Open());
             Assert.AreEqual(SFError.BROWSER_RESPONSE_TIMEOUT.GetAttribute<SFErrorAttr>().errorCode, thrown.ErrorCode);
         }
@@ -262,8 +262,8 @@ public void TestThatThrowsErrorWhenUrlIsNotWellFormedUriString()
         [Test]
         public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.PostAsync(uri.ToString(), new StringContent(""));
                 });
@@ -273,7 +273,7 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("CLIENT_STORE_TEMPORARY_CREDENTIAL=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             var thrown = Assert.Throws<SnowflakeDbException>(() => sfSession.Open());
             Assert.AreEqual(SFError.BROWSER_RESPONSE_WRONG_METHOD.GetAttribute<SFErrorAttr>().errorCode, thrown.ErrorCode);
         }
@@ -281,8 +281,8 @@ public void TestThatThrowsErrorWhenBrowserRequestMethodIsNotGet()
         [Test]
         public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     var urlWithoutQuery = uri.ToString().Substring(0, uri.ToString().IndexOf("?token="));
                     s_httpClient.GetAsync(urlWithoutQuery);
@@ -293,7 +293,7 @@ public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("CLIENT_STORE_TEMPORARY_CREDENTIAL=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             var thrown = Assert.Throws<SnowflakeDbException>(() => sfSession.Open());
             Assert.AreEqual(SFError.BROWSER_RESPONSE_INVALID_PREFIX.GetAttribute<SFErrorAttr>().errorCode, thrown.ErrorCode);
         }
@@ -301,8 +301,8 @@ public void TestThatThrowsErrorWhenBrowserRequestHasInvalidQuery()
         [Test]
         public void TestDefaultAuthenticationAsync()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -312,20 +312,20 @@ public void TestDefaultAuthenticationAsync()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             Task connectTask = sfSession.OpenAsync(CancellationToken.None);
             connectTask.Wait();
 
             Assert.IsTrue(sfSession._disableConsoleLogin);
-            t_browserOperations.Verify(b => b.OpenUrl(It.Is<Uri>(s => localhostRegex.IsMatch(s.ToString()))), Times.Once());
-            t_browserOperations.VerifyNoOtherCalls();
+            t_browserRunner.Verify(b => b.Run(It.Is<Uri>(s => localhostRegex.IsMatch(s.ToString()))), Times.Once());
+            t_browserRunner.VerifyNoOtherCalls();
         }
 
         [Test]
         public void TestConsoleLoginAsync()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     var port = HttpUtility.ParseQueryString(uri.Query).Get("browser_mode_redirect_port");
                     var browserUrl = $"http://localhost:{port}/?token=mockToken";
@@ -337,13 +337,13 @@ public void TestConsoleLoginAsync()
                 ProofKey = "mockProofKey",
             };
             var sfSession = new SFSession("disable_console_login=false;account=test;user=test;password=test;authenticator=externalbrowser;host=test.snowflakecomputing.com", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
             Task connectTask = sfSession.OpenAsync(CancellationToken.None);
             connectTask.Wait();
 
             Assert.IsFalse(sfSession._disableConsoleLogin);
-            t_browserOperations.Verify(b => b.OpenUrl(It.Is<Uri>(s => s.ToString().Contains("https://test.snowflakecomputing.com/console/login?"))), Times.Once());
-            t_browserOperations.VerifyNoOtherCalls();
+            t_browserRunner.Verify(b => b.Run(It.Is<Uri>(s => s.ToString().Contains("https://test.snowflakecomputing.com/console/login?"))), Times.Once());
+            t_browserRunner.VerifyNoOtherCalls();
         }
 
         [Test]
@@ -365,14 +365,14 @@ public void TestSSOTokenAsync()
             Task connectTask = sfSession.OpenAsync(CancellationToken.None);
             connectTask.Wait();
 
-            t_browserOperations.Verify(b => b.OpenUrl(It.IsAny<Uri>()), Times.Never());
+            t_browserRunner.Verify(b => b.Run(It.IsAny<Uri>()), Times.Never());
         }
 
         [Test]
         public void TestThatTokenIsStoredWhenCacheIsEnabledAsync()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -388,7 +388,7 @@ public void TestThatTokenIsStoredWhenCacheIsEnabledAsync()
                 IdToken = expectedIdToken
             };
             var sfSession = new SFSession($"client_store_temporary_credential=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(string.Empty, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
@@ -401,8 +401,8 @@ public void TestThatTokenIsStoredWhenCacheIsEnabledAsync()
         [Test]
         public void TestThatTokenIsNotStoredWhenCacheIsDisabledAsync()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -418,7 +418,7 @@ public void TestThatTokenIsNotStoredWhenCacheIsDisabledAsync()
                 IdToken = expectedIdToken
             };
             var sfSession = new SFSession($"client_store_temporary_credential=false;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(string.Empty, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
@@ -431,8 +431,8 @@ public void TestThatTokenIsNotStoredWhenCacheIsDisabledAsync()
         [Test]
         public void TestThatRetriesAuthenticationForInvalidIdTokenAsync()
         {
-            t_browserOperations
-                .Setup(b => b.OpenUrl(It.IsAny<Uri>()))
+            t_browserRunner
+                .Setup(b => b.Run(It.IsAny<Uri>()))
                 .Callback((Uri uri) => {
                     s_httpClient.GetAsync(uri.ToString());
                 });
@@ -453,14 +453,14 @@ public void TestThatRetriesAuthenticationForInvalidIdTokenAsync()
                 ThrowInvalidIdToken = true
             };
             var sfSession = new SFSession($"CLIENT_STORE_TEMPORARY_CREDENTIAL=true;account=test;user={user};password=test;authenticator=externalbrowser;host={host}", new SessionPropertiesContext(), restRequester);
-            SFExternalBrowserTest.SetAuthenticatorWithMockBrowser(sfSession, t_browserOperations.Object);
+            SFExternalBrowserTest.SetAuthenticatorWithMockBrowser(sfSession, t_browserRunner.Object);
 
             Assert.AreEqual(invalidIdToken, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
 
             Task connectTask = sfSession.OpenAsync(CancellationToken.None);
             connectTask.Wait();
 
-            t_browserOperations.Verify(b => b.OpenUrl(It.IsAny<Uri>()), Times.Once);
+            t_browserRunner.Verify(b => b.Run(It.IsAny<Uri>()), Times.Once);
             Assert.AreEqual(expectedIdToken, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
         }
 
@@ -487,9 +487,9 @@ public void TestThatDoesNotRetryAuthenticationForNonInvalidIdTokenExceptionAsync
             Assert.AreEqual(expectedIdToken, SnowflakeCredentialManagerFactory.GetCredentialManager().GetCredentials(key));
         }
 
-        private static void SetAuthenticatorWithMockBrowser(SFSession session, BrowserOperations browserOperations)
+        private static void SetAuthenticatorWithMockBrowser(SFSession session, IWebBrowserRunner browserRunner)
         {
-            var authenticator = new ExternalBrowserAuthenticator(session, browserOperations);
+            var authenticator = new ExternalBrowserAuthenticator(session, browserRunner);
             session.authenticator = authenticator;
         }
     }
diff --git a/Snowflake.Data/Core/Authenticator/Browser/AuthorizationCodeError.cs b/Snowflake.Data/Core/Authenticator/Browser/BrowserError.cs
similarity index 87%
rename from Snowflake.Data/Core/Authenticator/Browser/AuthorizationCodeError.cs
rename to Snowflake.Data/Core/Authenticator/Browser/BrowserError.cs
index d57f6503d..a19939a51 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/AuthorizationCodeError.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/BrowserError.cs
@@ -3,7 +3,7 @@
 
 namespace Snowflake.Data.Core.Authenticator.Browser
 {
-    internal class AuthorizationCodeError : IBrowserError
+    internal class BrowserError : IBrowserError
     {
         public string BrowserMessage { get; set; }
 
diff --git a/Snowflake.Data/Core/Authenticator/Browser/IWebBrowserRunner.cs b/Snowflake.Data/Core/Authenticator/Browser/IWebBrowserRunner.cs
index 7c4ca0c4a..7f5e719b3 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/IWebBrowserRunner.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/IWebBrowserRunner.cs
@@ -1,7 +1,9 @@
+using System;
+
 namespace Snowflake.Data.Core.Authenticator.Browser
 {
     internal interface IWebBrowserRunner
     {
-        void Run(string url);
+        void Run(Uri uri);
     }
 }
diff --git a/Snowflake.Data/Core/Authenticator/Browser/Url.cs b/Snowflake.Data/Core/Authenticator/Browser/Url.cs
new file mode 100644
index 000000000..18e1573bc
--- /dev/null
+++ b/Snowflake.Data/Core/Authenticator/Browser/Url.cs
@@ -0,0 +1,19 @@
+namespace Snowflake.Data.Core.Authenticator.Browser
+{
+    internal class Url
+    {
+        public string Value { get; }
+
+        public string ValueWithoutSecrets { get; }
+
+        public Url(string url) : this(url, url)
+        {
+        }
+
+        public Url(string url, string maskedUrl)
+        {
+            Value = url;
+            ValueWithoutSecrets = maskedUrl;
+        }
+    }
+}
diff --git a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserListener.cs b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserListener.cs
index 50844e657..a73808fe5 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserListener.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserListener.cs
@@ -19,6 +19,7 @@ internal class WebBrowserListener<T> : IDisposable
         private T _result;
         private string _browserError;
         private Exception _exception;
+        private bool _isDisposed;
 
         private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<WebBrowserListener<T>>();
 
@@ -32,6 +33,7 @@ public WebBrowserListener(HttpListener httpListener, Func<HttpListenerRequest, R
             _result = null;
             _browserError = null;
             _exception = null;
+            _isDisposed = false;
         }
 
         public T WaitAndGetResult(TimeSpan timeout)
@@ -60,12 +62,24 @@ public T WaitAndGetResult(TimeSpan timeout)
 
         private void GetContextCallback(IAsyncResult result)
         {
+            if (_isDisposed)
+                return;
             HttpListener httpListener = (HttpListener) result.AsyncState;
             if (httpListener.IsListening)
             {
-                HttpListenerContext context = httpListener.EndGetContext(result);
-                HttpListenerRequest request = context.Request;
+                HttpListenerContext context = null;
+                try
+                {
+                    context = httpListener.EndGetContext(result);
+                }
+                catch (HttpListenerException ex)
+                {
+                    s_logger.Error("Error while trying to get context from HttpListener", ex);
+                    WakeUpAwaitingThread();
+                    return;
+                }
 
+                HttpListenerRequest request = context.Request;
                 bool success;
                 try
                 {
@@ -94,7 +108,23 @@ private void GetContextCallback(IAsyncResult result)
                     RespondToBrowserWithError(context);
                 }
             }
-            _successEvent.Set();
+            WakeUpAwaitingThread();
+        }
+
+        private void WakeUpAwaitingThread()
+        {
+            try
+            {
+                _successEvent.Set();
+            }
+            catch (ObjectDisposedException)
+            {
+                s_logger.Warn("Could not wake up the thread waiting for the browser response because the resource was already disposed");
+            }
+            catch (Exception exception)
+            {
+                s_logger.Warn("Could not wake up the thread waiting for the browser response because of error", exception);
+            }
         }
 
         private void RespondToBrowser(HttpListenerContext context)
@@ -136,8 +166,12 @@ private void RespondToBrowserWithError(HttpListenerContext context)
 
         public void Dispose()
         {
-            ((IDisposable)_httpListener)?.Dispose();
-            _successEvent?.Dispose();
+            if (!_isDisposed)
+            {
+                _isDisposed = true;
+                ((IDisposable)_httpListener)?.Dispose();
+                _successEvent?.Dispose();
+            }
         }
     }
 }
diff --git a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserRunner.cs b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserRunner.cs
index 88eed98a5..ee068b154 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserRunner.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserRunner.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Snowflake.Data.Client;
@@ -12,14 +13,16 @@ internal WebBrowserRunner()
         {
         }
 
-        public virtual void Run(string url)
+        public virtual void Run(Uri uri)
         {
-            // The following code is learnt from https://brockallen.com/2016/09/24/process-start-for-urls-on-net-core/
-            // hack because of this: https://github.com/dotnet/corefx/issues/10361
+            var url = uri.AbsoluteUri;
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
-                url = url.Replace("&", "^&");
-                Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { UseShellExecute = true });
+                ProcessStartInfo startInfo = new ProcessStartInfo();
+                startInfo.FileName = System.IO.Path.Combine(Environment.SystemDirectory, "rundll32.exe");
+                startInfo.Arguments = $"url.dll,FileProtocolHandler {url}";
+                startInfo.UseShellExecute = false;
+                Process.Start(startInfo);
             }
             else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
             {
diff --git a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
index 8c3c2621e..4477d800c 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
@@ -7,6 +7,8 @@ namespace Snowflake.Data.Core.Authenticator.Browser
 {
     internal class WebBrowserStarter
     {
+        private const string UrlRegexString = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
+
         private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<WebBrowserStarter>();
 
         private readonly IWebBrowserRunner _runner;
@@ -18,26 +20,21 @@ internal WebBrowserStarter(IWebBrowserRunner runner)
             _runner = runner;
         }
 
-        public void StartBrowser(string url)
+        public void StartBrowser(Url url)
         {
-            string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
-            Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success)
-            {
-                ThrowInvalidBrowserUrlException();
-            }
-            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
-            {
-                ThrowInvalidBrowserUrlException();
-            }
-            _runner.Run(url);
+            ValidateUrl(url);
+            var uri = new Uri(url.Value);
+            _runner.Run(uri);
         }
 
-        private void ThrowInvalidBrowserUrlException()
+        private void ValidateUrl(Url url)
         {
-            var errorMessage = "Failed to start browser. Invalid url.";
-            s_logger.Error(errorMessage);
-            throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL, errorMessage);
+            Match urlMatch = Regex.Match(url.Value, UrlRegexString, RegexOptions.IgnoreCase);
+            if (!urlMatch.Success || !Uri.IsWellFormedUriString(url.Value, UriKind.Absolute))
+            {
+                s_logger.Error("Failed to start browser. Invalid url.");
+                throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL, url.ValueWithoutSecrets);
+            }
         }
     }
 }
diff --git a/Snowflake.Data/Core/Authenticator/Browser/WebListenerStarter.cs b/Snowflake.Data/Core/Authenticator/Browser/WebListenerStarter.cs
index 692f010f1..d06e474f7 100644
--- a/Snowflake.Data/Core/Authenticator/Browser/WebListenerStarter.cs
+++ b/Snowflake.Data/Core/Authenticator/Browser/WebListenerStarter.cs
@@ -9,11 +9,19 @@ internal class WebListenerStarter
         public static WebListenerStarter Instance = new WebListenerStarter();
 
         public virtual HttpListener StartHttpListener(string url)
+        {
+            return StartHttpListener(new [] { url });
+        }
+
+        public virtual HttpListener StartHttpListener(string[] urls)
         {
             var listener = new HttpListener();
             try
             {
-                listener.Prefixes.Add(url);
+                foreach (var url in urls)
+                {
+                    listener.Prefixes.Add(url);
+                }
                 listener.Start();
             }
             catch (Exception)
diff --git a/Snowflake.Data/Core/Authenticator/ChallengeProvider.cs b/Snowflake.Data/Core/Authenticator/ChallengeProvider.cs
index 32c7b5aa4..c96dfd3a7 100644
--- a/Snowflake.Data/Core/Authenticator/ChallengeProvider.cs
+++ b/Snowflake.Data/Core/Authenticator/ChallengeProvider.cs
@@ -9,7 +9,7 @@ public virtual string GenerateState()
             return Guid.NewGuid().ToString().Replace("-", "");
         }
 
-        public CodeVerifier GenerateCodeVerifier()
+        public virtual CodeVerifier GenerateCodeVerifier()
         {
             Random random = new Random();
             byte[] randomness = new byte[48];
diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs
index 95978b037..139526316 100644
--- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs
+++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs
@@ -1,14 +1,13 @@
 using System;
 using System.Net;
-using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 using Snowflake.Data.Log;
 using Snowflake.Data.Client;
-using System.Text.RegularExpressions;
 using System.Collections.Generic;
 using Snowflake.Data.Core.CredentialManager;
 using System.Security;
+using Snowflake.Data.Core.Authenticator.Browser;
 using Snowflake.Data.Core.Tools;
 
 namespace Snowflake.Data.Core.Authenticator
@@ -21,32 +20,30 @@ class ExternalBrowserAuthenticator : BaseAuthenticator, IAuthenticator
         public const string AUTH_NAME = "externalbrowser";
         private static readonly SFLogger logger = SFLoggerFactory.GetLogger<ExternalBrowserAuthenticator>();
         private static readonly string TOKEN_REQUEST_PREFIX = "?token=";
-        private static readonly byte[] SUCCESS_RESPONSE = System.Text.Encoding.UTF8.GetBytes(
+
+        private static readonly string SuccessResponse =
             "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"/>" +
             "<title> SAML Response for Snowflake </title></head>" +
             "<body>Your identity was confirmed and propagated to Snowflake .NET driver. You can close this window now and go back where you started from." +
-            "</body></html>"
-            );
-        private static readonly byte[] ERROR_RESPONSE = System.Text.Encoding.UTF8.GetBytes(
+            "</body></html>";
+
+        private static readonly string ErrorResponse =
             "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"/>" +
             "<title> SAML Response for Snowflake </title></head>" +
             "<body>Authentication failed due to an error and was unable to extract a SAML response token." +
-            "</body></html>"
-            );
+            "</body></html>";
+
         // The saml token to send in the login request.
         private string _samlResponseToken;
         // The proof key to send in the login request.
         private string _proofKey;
-        // Event for successful authentication.
-        private ManualResetEvent _successEvent;
-        // Placeholder in case an exception occurs while extracting the token from the browser response.
-        private Exception _tokenExtractionException;
 
         internal string _idTokenKey = "";
 
         private SecureString _idToken;
 
-        internal BrowserOperations _browserOperations = BrowserOperations.Instance;
+        private readonly WebBrowserStarter _browserStarter = WebBrowserStarter.Instance;
+        private readonly WebListenerStarter _listenerStarter = WebListenerStarter.Instance;
 
         /// <summary>
         /// Constructor of the External authenticator
@@ -65,9 +62,9 @@ internal ExternalBrowserAuthenticator(SFSession session) : base(session, AUTH_NA
             }
         }
 
-        internal ExternalBrowserAuthenticator(SFSession session, BrowserOperations browserOperations) : this(session)
+        internal ExternalBrowserAuthenticator(SFSession session, IWebBrowserRunner browserRunner) : this(session)
         {
-            _browserOperations = browserOperations;
+            _browserStarter = new WebBrowserStarter(browserRunner);
         }
 
         public static bool IsExternalBrowserAuthenticator(string authenticator) =>
@@ -82,17 +79,14 @@ public async Task AuthenticateAsync(CancellationToken cancellationToken)
             _idToken = string.IsNullOrEmpty(idToken) ? null : SecureStringHelper.Encode(idToken);
             if (_idToken == null)
             {
-                int localPort = GetRandomUnusedPort();
-                using (var httpListener = GetHttpListener(localPort))
+                int localPort = _listenerStarter.GetRandomUnusedPort();
+                var localhostEndpoints = GetLocalhostEndpoints(localPort);
+                using (var httpListener = _listenerStarter.StartHttpListener(localhostEndpoints))
                 {
-                    httpListener.Start();
                     logger.Debug("Get IdpUrl and ProofKey");
                     var loginUrl = await GetIdpUrlAndProofKeyAsync(localPort, cancellationToken).ConfigureAwait(false);
-                    logger.Debug("Open browser");
-                    StartBrowser(loginUrl);
                     logger.Debug("Get the redirect SAML request");
-                    GetRedirectSamlRequest(httpListener);
-                    httpListener.Stop();
+                    _samlResponseToken = GetRedirectSamlRequest(httpListener, loginUrl);
                 }
             }
 
@@ -123,17 +117,14 @@ public void Authenticate()
             _idToken = string.IsNullOrEmpty(idToken) ? null : SecureStringHelper.Encode(idToken);
             if (_idToken == null)
             {
-                int localPort = GetRandomUnusedPort();
-                using (var httpListener = GetHttpListener(localPort))
+                int localPort = _listenerStarter.GetRandomUnusedPort();
+                var localhostEndpoints = GetLocalhostEndpoints(localPort);
+                using (var httpListener = _listenerStarter.StartHttpListener(localhostEndpoints))
                 {
-                    httpListener.Start();
                     logger.Debug("Get IdpUrl and ProofKey");
                     var loginUrl = GetIdpUrlAndProofKey(localPort);
-                    logger.Debug("Open browser");
-                    StartBrowser(loginUrl);
                     logger.Debug("Get the redirect SAML request");
-                    GetRedirectSamlRequest(httpListener);
-                    httpListener.Stop();
+                    _samlResponseToken = GetRedirectSamlRequest(httpListener, loginUrl);
                 }
             }
 
@@ -206,120 +197,54 @@ await session.restRequester.PostAsync<AuthenticatorResponse>(
             }
         }
 
-        private void GetRedirectSamlRequest(HttpListener httpListener)
+        private string GetRedirectSamlRequest(HttpListener httpListener, string loginUrl)
         {
-            _successEvent = new ManualResetEvent(false);
-            _tokenExtractionException = null;
-            httpListener.BeginGetContext(GetContextCallback, httpListener);
             var timeoutInSec = int.Parse(session.properties[SFSessionProperty.BROWSER_RESPONSE_TIMEOUT]);
-            if (!_successEvent.WaitOne(timeoutInSec * 1000))
-            {
-                logger.Error("Browser response timeout has been reached");
-                throw new SnowflakeDbException(SFError.BROWSER_RESPONSE_TIMEOUT, timeoutInSec);
-            }
-            if (_tokenExtractionException != null)
+            var timeout = TimeSpan.FromSeconds(timeoutInSec);
+            var extractor = new Func<HttpListenerRequest, Result<ExternalBrowserToken, IBrowserError>>(ValidateAndExtractToken);
+            using (var browserListener = new WebBrowserListener<ExternalBrowserToken>(httpListener, extractor, SuccessResponse, ErrorResponse))
             {
-                throw _tokenExtractionException;
+                logger.Debug("Open browser");
+                _browserStarter.StartBrowser(new Url(loginUrl));
+                return browserListener.WaitAndGetResult(timeout).Token;
             }
         }
 
-        private void GetContextCallback(IAsyncResult result)
-        {
-            HttpListener httpListener = (HttpListener)result.AsyncState;
-            if (httpListener.IsListening)
-            {
-                HttpListenerContext context = null;
-                try
-                {
-                    context = httpListener.EndGetContext(result);
-                }
-                catch (HttpListenerException ex)
-                {
-                    logger.Error("Error while trying to get context from HttpListener", ex);
-                }
-                if (context != null)
-                {
-                    HttpListenerRequest request = context.Request;
-
-                    _samlResponseToken = ValidateAndExtractToken(request);
-                    HttpListenerResponse response = context.Response;
-                    try
-                    {
-                        using (var output = response.OutputStream)
-                        {
-                            if (!string.IsNullOrEmpty(_samlResponseToken))
-                            {
-                                output.Write(SUCCESS_RESPONSE, 0, SUCCESS_RESPONSE.Length);
-                            }
-                            else
-                            {
-                                output.Write(ERROR_RESPONSE, 0, ERROR_RESPONSE.Length);
-                            }
-                        }
-                    }
-                    catch
-                    {
-                        // Ignore the exception as it does not affect the overall authentication flow
-                        logger.Warn("External browser response not sent out");
-                    }
-                }
-            }
-
-            _successEvent.Set();
-        }
+        private static string[] GetLocalhostEndpoints(int port) =>
+            new[] { $"http://{IPAddress.Loopback}:{port}/", $"http://localhost:{port}/" };
 
-        private static int GetRandomUnusedPort()
-        {
-            var listener = new TcpListener(IPAddress.Loopback, 0);
-            listener.Start();
-            var port = ((IPEndPoint)listener.LocalEndpoint).Port;
-            listener.Stop();
-            return port;
-        }
-
-        private static HttpListener GetHttpListener(int port)
-        {
-            HttpListener listener = new HttpListener();
-            listener.Prefixes.Add($"http://{IPAddress.Loopback}:{port}/");
-            listener.Prefixes.Add($"http://localhost:{port}/");
-            return listener;
-        }
-
-        private void StartBrowser(string url)
-        {
-            string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
-            Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
-            {
-                logger.Error("Failed to start browser. Invalid url.");
-                throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL, url);
-            }
-            var uri = new Uri(url);
-            if (url != uri.ToString())
-            {
-                logger.Error("Failed to start browser. Invalid uri.");
-                throw new SnowflakeDbException(SFError.INVALID_BROWSER_URL, url);
-            }
-            _browserOperations.OpenUrl(uri);
-        }
-
-        private string ValidateAndExtractToken(HttpListenerRequest request)
+        private Result<ExternalBrowserToken, IBrowserError> ValidateAndExtractToken(HttpListenerRequest request)
         {
             if (request.HttpMethod != "GET")
             {
                 logger.Error("Failed to extract token due to invalid HTTP method.");
-                _tokenExtractionException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.Url.Query);
-                return null;
+                return Result<ExternalBrowserToken, IBrowserError>.CreateError(new BrowserError
+                {
+                    BrowserMessage = ErrorResponse,
+                    Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.HttpMethod)
+                });
             }
 
             if (request.Url.Query == null || !request.Url.Query.StartsWith(TOKEN_REQUEST_PREFIX))
             {
                 logger.Error("Failed to extract token due to invalid query.");
-                _tokenExtractionException = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query);
-                return null;
+                return Result<ExternalBrowserToken, IBrowserError>.CreateError(new BrowserError
+                {
+                    BrowserMessage = ErrorResponse,
+                    Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_INVALID_PREFIX, request.Url.Query)
+                });
             }
 
-            return Uri.UnescapeDataString(request.Url.Query.Substring(TOKEN_REQUEST_PREFIX.Length));
+            var token = Uri.UnescapeDataString(request.Url.Query.Substring(TOKEN_REQUEST_PREFIX.Length));
+            if (string.IsNullOrEmpty(token))
+            {
+                return Result<ExternalBrowserToken, IBrowserError>.CreateError(new BrowserError
+                {
+                    BrowserMessage = ErrorResponse,
+                    Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, "could not retrieve token")
+                });
+            }
+            return Result<ExternalBrowserToken, IBrowserError>.CreateResult(new ExternalBrowserToken(token));
         }
 
         private SFRestRequest BuildAuthenticatorRestRequest(int port)
diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserToken.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserToken.cs
new file mode 100644
index 000000000..75753dc18
--- /dev/null
+++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserToken.cs
@@ -0,0 +1,12 @@
+namespace Snowflake.Data.Core.Authenticator
+{
+    internal class ExternalBrowserToken
+    {
+        public string Token { get; set; }
+
+        public ExternalBrowserToken(string token)
+        {
+            Token = token;
+        }
+    }
+}
diff --git a/Snowflake.Data/Core/Authenticator/OAuthAuthorizationCodeAuthenticator.cs b/Snowflake.Data/Core/Authenticator/OAuthAuthorizationCodeAuthenticator.cs
index f2ad08af8..24f971adf 100644
--- a/Snowflake.Data/Core/Authenticator/OAuthAuthorizationCodeAuthenticator.cs
+++ b/Snowflake.Data/Core/Authenticator/OAuthAuthorizationCodeAuthenticator.cs
@@ -121,8 +121,8 @@ private OAuthAuthorizationCodeResponse ExecuteAuthorizationCodeRequest(OAuthAuth
             using (var httpListener = StartListenerUpdatingRedirectUri(request))
             using (var browserListener = new WebBrowserListener<OAuthAuthorizationCodeResponse>(httpListener, extractor, BrowserSuccessResponse, BrowserUnexpectedErrorResponse))
             {
-                var authorizationCodeUrlString = request.GetUrl();
-                _browserStarter.StartBrowser(authorizationCodeUrlString);
+                var authorizationCodeUrl = request.GetUrl();
+                _browserStarter.StartBrowser(authorizationCodeUrl);
                 return browserListener.WaitAndGetResult(timeout);
             }
         }
@@ -131,7 +131,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
         {
             if (request.HttpMethod != "GET")
             {
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = BadRequestError("<br><b>Error</b>: Expected GET http method.</br>"),
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_WRONG_METHOD, request.HttpMethod)
@@ -140,7 +140,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
 
             if (string.IsNullOrEmpty(request.Url.Query))
             {
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = BadRequestError("<br><b>Error</b>: No query parameters</br>"),
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, "No query parameters")
@@ -154,7 +154,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
                 var errorMessage = "<br>Identity Provider failed with error: </br>"
                                    + $"<br><b>Error</b>: {HttpUtility.HtmlEncode(error)}</br>"
                                    + $"<br><b>Error Description</b>: {HttpUtility.HtmlEncode(errorDescription)}</br>";
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = BadRequestError(errorMessage),
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, $"Authorization code response has error '{error}' and description '{errorDescription}'")
@@ -164,7 +164,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
             var state = parameters.Get("state");
             if (string.IsNullOrEmpty(authorizationCode))
             {
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = BadRequestError("<br><b>Error</b>: Authorization code is required in the authorization code response</br>"),
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, "Authorization code is required in the authorization code response")
@@ -172,7 +172,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
             }
             if (string.IsNullOrEmpty(state))
             {
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = BadRequestError("<br><b>Error</b>: State is required in the authorization code response</br>"),
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, "State is required in the authorization code response")
@@ -180,7 +180,7 @@ private Result<OAuthAuthorizationCodeResponse, IBrowserError> ValidateAndExtract
             }
             if (state != expectedState)
             {
-                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new AuthorizationCodeError
+                return Result<OAuthAuthorizationCodeResponse, IBrowserError>.CreateError(new BrowserError
                 {
                     BrowserMessage = "<br><b>Error</b>: Identity Provider did not provide expected state parameter! It might indicate an XSS attack.</br>",
                     Exception = new SnowflakeDbException(SFError.BROWSER_RESPONSE_ERROR, "State mismatch for authorization code request and response")
diff --git a/Snowflake.Data/Core/Rest/OAuthAuthorizationCodeRequest.cs b/Snowflake.Data/Core/Rest/OAuthAuthorizationCodeRequest.cs
index 5b7c2ffb8..6a2f5e42c 100644
--- a/Snowflake.Data/Core/Rest/OAuthAuthorizationCodeRequest.cs
+++ b/Snowflake.Data/Core/Rest/OAuthAuthorizationCodeRequest.cs
@@ -1,5 +1,6 @@
 using System.Text;
 using System.Web;
+using Snowflake.Data.Core.Authenticator.Browser;
 
 namespace Snowflake.Data.Core.Rest
 {
@@ -12,17 +13,22 @@ internal class OAuthAuthorizationCodeRequest
         public string CodeChallenge { get; set; }
         public string State { get; set; }
 
-        public string GetUrl()
+        public Url GetUrl() =>
+            new Url(CreateUrl(true), CreateUrl(false));
+
+        private string CreateUrl(bool includeSecrets)
         {
             var urlBuilder = new StringBuilder();
             urlBuilder.Append(AuthorizationEndpoint);
-            urlBuilder.Append("?client_id=").Append(HttpUtility.UrlEncode(ClientId));
+            var clientId = includeSecrets ? HttpUtility.UrlEncode(ClientId) : "****";
+            urlBuilder.Append("?client_id=").Append(clientId);
             urlBuilder.Append("&response_type=").Append(HttpUtility.UrlEncode("code"));
             urlBuilder.Append("&redirect_uri=").Append(HttpUtility.UrlEncode(RedirectUri));
             urlBuilder.Append("&scope=").Append(HttpUtility.UrlEncode(AuthorizationScope));
             urlBuilder.Append("&code_challenge=").Append(HttpUtility.UrlEncode(CodeChallenge));
             urlBuilder.Append("&code_challenge_method=").Append(HttpUtility.UrlEncode("S256"));
-            urlBuilder.Append("&state=").Append(HttpUtility.UrlEncode(State));
+            var state = includeSecrets ? HttpUtility.UrlEncode(State) : "****";
+            urlBuilder.Append("&state=").Append(state);
             return urlBuilder.ToString();
         }
     }
diff --git a/Snowflake.Data/Core/Tools/BrowserOperations.cs b/Snowflake.Data/Core/Tools/BrowserOperations.cs
deleted file mode 100644
index e07491c85..000000000
--- a/Snowflake.Data/Core/Tools/BrowserOperations.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using Snowflake.Data.Client;
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Snowflake.Data.Core.Tools
-{
-    internal class BrowserOperations
-    {
-        public static readonly BrowserOperations Instance = new BrowserOperations();
-
-        internal virtual void OpenUrl(Uri uri)
-        {
-            var url = uri.ToString();
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                ProcessStartInfo startInfo = new ProcessStartInfo();
-                startInfo.FileName = System.IO.Path.Combine(Environment.SystemDirectory, "rundll32.exe");
-                startInfo.Arguments = $"url.dll,FileProtocolHandler {url}";
-                startInfo.UseShellExecute = false;
-                Process.Start(startInfo);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-            {
-                Process.Start("xdg-open", url);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                Process.Start("open", url);
-            }
-            else
-            {
-                throw new SnowflakeDbException(SFError.UNSUPPORTED_PLATFORM);
-            }
-        }
-    }
-}


Reviews:
user: login: sfc-gh-dstempniak
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T14:35:38Z
user: login: sfc-gh-pbulawa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T07:16:35Z
user: login: sfc-gh-knozderko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:50:00Z
user: login: sfc-gh-knozderko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:50:15Z
user: login: sfc-gh-mkubik
state: APPROVED
author_association: NONE
submitted_at: 2025-04-22T13:11:06Z

Review comments:
diff_hunk: @@ -22,22 +22,22 @@ public void StartBrowser(string url)
         {
             string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
             Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success)
+            if (!m.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
             {
                 ThrowInvalidBrowserUrlException();
             }
-            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
+            var uri = new Uri(url);
+            if (url != uri.ToString())
path: Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
user: login: sfc-gh-dstempniak
body: Why do we need this check? `IsWellFormedUriString` above is not enough?
created_at: 2025-04-16T14:28:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,23 +21,24 @@ internal WebBrowserStarter(IWebBrowserRunner runner)
         public void StartBrowser(string url)
         {
             string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
-            Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success)
+            Match urlMatch = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
+            if (!urlMatch.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
             {
                 ThrowInvalidBrowserUrlException();
             }
-            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
+            var uri = new Uri(url);
+            var uriMatch = Regex.Match(uri.ToString(), regexStr, RegexOptions.IgnoreCase);
+            if (!uriMatch.Success || !Uri.IsWellFormedUriString(uri.ToString(), UriKind.Absolute))
path: Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
user: login: sfc-gh-pbulawa
body: It seems that `Uri.IsWellFormedUriString` is redundant with the one at line 25.
created_at: 2025-04-17T07:14:21Z
author_association: COLLABORATOR
diff_hunk: @@ -22,22 +22,22 @@ public void StartBrowser(string url)
         {
             string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
             Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success)
+            if (!m.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
             {
                 ThrowInvalidBrowserUrlException();
             }
-            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
+            var uri = new Uri(url);
+            if (url != uri.ToString())
path: Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
user: login: sfc-gh-knozderko
body: this was removed
created_at: 2025-04-17T15:50:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,23 +21,24 @@ internal WebBrowserStarter(IWebBrowserRunner runner)
         public void StartBrowser(string url)
         {
             string regexStr = "^http(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\&\\(\\)\\/\\\\\\+&%\\$#_=@]*)?$";
-            Match m = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
-            if (!m.Success)
+            Match urlMatch = Regex.Match(url, regexStr, RegexOptions.IgnoreCase);
+            if (!urlMatch.Success || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
             {
                 ThrowInvalidBrowserUrlException();
             }
-            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
+            var uri = new Uri(url);
+            var uriMatch = Regex.Match(uri.ToString(), regexStr, RegexOptions.IgnoreCase);
+            if (!uriMatch.Success || !Uri.IsWellFormedUriString(uri.ToString(), UriKind.Absolute))
path: Snowflake.Data/Core/Authenticator/Browser/WebBrowserStarter.cs
user: login: sfc-gh-knozderko
body: it was removed
created_at: 2025-04-17T15:50:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: sfc-gh-snowflakedb-snyk-sa
created_at: 2025-04-15T15:40:41Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/snowflakedb-sca-scanning-public-repo/pr-checks/3c233937-4621-414f-974d-e10c71517030?refs=53ae9903290b6c9dcc2eca57ac0166727fc9b714&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/snowflakedb-sca-scanning-public-repo/pr-checks/3c233937-4621-414f-974d-e10c71517030/license?refs=53ae9903290b6c9dcc2eca57ac0166727fc9b714&source=prChecksComment)



author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-15T16:14:22Z
body: ## [Codecov](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) Report
Attention: Patch coverage is `81.45161%` with `23 lines` in your changes missing coverage. Please review.
> Project coverage is 87.66%. Comparing base [(`786da43`)](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/commit/786da43dddad8a133b8e24a59fcbb3c31b983193?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) to head [(`53ae990`)](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/commit/53ae9903290b6c9dcc2eca57ac0166727fc9b714?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).
> Report is 1 commits behind head on oauth_flows.

| [Files with missing lines](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) | Patch % | Lines |
|---|---|---|
| [...Core/Authenticator/ExternalBrowserAuthenticator.cs](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&filepath=Snowflake.Data%2FCore%2FAuthenticator%2FExternalBrowserAuthenticator.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-U25vd2ZsYWtlLkRhdGEvQ29yZS9BdXRoZW50aWNhdG9yL0V4dGVybmFsQnJvd3NlckF1dGhlbnRpY2F0b3IuY3M=) | 83.33% | [6 Missing and 1 partial :warning: ](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |
| [...a/Core/Authenticator/Browser/WebBrowserListener.cs](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&filepath=Snowflake.Data%2FCore%2FAuthenticator%2FBrowser%2FWebBrowserListener.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-U25vd2ZsYWtlLkRhdGEvQ29yZS9BdXRoZW50aWNhdG9yL0Jyb3dzZXIvV2ViQnJvd3Nlckxpc3RlbmVyLmNz) | 81.81% | [4 Missing and 2 partials :warning: ](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |
| [...ata/Core/Authenticator/Browser/WebBrowserRunner.cs](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&filepath=Snowflake.Data%2FCore%2FAuthenticator%2FBrowser%2FWebBrowserRunner.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-U25vd2ZsYWtlLkRhdGEvQ29yZS9BdXRoZW50aWNhdG9yL0Jyb3dzZXIvV2ViQnJvd3NlclJ1bm5lci5jcw==) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |
| [...thenticator/OAuthAuthorizationCodeAuthenticator.cs](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&filepath=Snowflake.Data%2FCore%2FAuthenticator%2FOAuthAuthorizationCodeAuthenticator.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-U25vd2ZsYWtlLkRhdGEvQ29yZS9BdXRoZW50aWNhdG9yL09BdXRoQXV0aG9yaXphdGlvbkNvZGVBdXRoZW50aWNhdG9yLmNz) | 50.00% | [4 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |

<details><summary>Additional details and impacted files</summary>


```diff
@@               Coverage Diff               @@
##           oauth_flows    #1145      +/-   ##
===============================================
+ Coverage        87.49%   87.66%   +0.17%     
===============================================
  Files              155      156       +1     
  Lines            13901    13871      -30     
  Branches          1422     1417       -5     
===============================================
- Hits             12162    12160       -2     
+ Misses            1379     1355      -24     
+ Partials           360      356       -4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/snowflakedb/snowflake-connector-net/pull/1145?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE