Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use auth code flow rather than acquire_token_interactive #32539

Merged
merged 4 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bugs Fixed

- Fixed issue InteractiveBrowserCredential does not hand over to next credential in chain if no browser is supported.
xiangyan99 marked this conversation as resolved.
Show resolved Hide resolved

### Other Changes

## 1.29.4 (2023-09-07)
Expand Down
109 changes: 45 additions & 64 deletions sdk/identity/azure-identity/azure/identity/_credentials/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import subprocess
import webbrowser
from urllib.parse import urlparse
import msal

from azure.core.exceptions import ClientAuthenticationError

Expand Down Expand Up @@ -81,77 +80,59 @@ def __init__(self, **kwargs: Any) -> None:
super(InteractiveBrowserCredential, self).__init__(client_id=client_id, **kwargs)

@wrap_exceptions
def _request_token(self, *scopes: str, **kwargs: Any) -> Dict:
scopes = list(scopes) # type: ignore
claims = kwargs.get("claims")
app = self._get_app(**kwargs)
if isinstance(app, msal.ConfidentialClientApplication):
server = None
if self._parsed_url:
def _request_token(self, *scopes: str, **kwargs) -> Dict:

# start an HTTP server to receive the redirect
server = None
redirect_uri: str = ""
if self._parsed_url:
try:
redirect_uri = "http://{}:{}".format(self._parsed_url.hostname, self._parsed_url.port)
server = self._server_class(self._parsed_url.hostname, self._parsed_url.port, timeout=self._timeout)
except socket.error as ex:
raise CredentialUnavailableError(message="Couldn't start an HTTP server on " + redirect_uri) from ex
else:
for port in range(8400, 9000):
try:
server = self._server_class(self._parsed_url.hostname, self._parsed_url.port, timeout=self._timeout)
except socket.error as ex:
raise CredentialUnavailableError(message="Couldn't start an HTTP server on " + redirect_uri) from ex
else:
for port in range(8400, 9000):
try:
server = self._server_class("localhost", port, timeout=self._timeout)
redirect_uri = "http://localhost:{}".format(port)
break
except socket.error:
continue # keep looking for an open port

if not server:
raise CredentialUnavailableError(message="Couldn't start an HTTP server on localhost")

flow = app.initiate_auth_code_flow(
scopes,
redirect_uri=redirect_uri,
prompt="select_account",
claims_challenge=claims,
login_hint=self._login_hint,
)
if "auth_uri" not in flow:
raise CredentialUnavailableError("Failed to begin authentication flow")
server = self._server_class("localhost", port, timeout=self._timeout)
redirect_uri = "http://localhost:{}".format(port)
break
except socket.error:
continue # keep looking for an open port

if not _open_browser(flow["auth_uri"]):
raise CredentialUnavailableError(message="Failed to open a browser")
if not server:
raise CredentialUnavailableError(message="Couldn't start an HTTP server on localhost")

# block until the server times out or receives the post-authentication redirect
response = server.wait_for_redirect()
if not response:
raise ClientAuthenticationError(
# get the url the user must visit to authenticate
scopes = list(scopes) # type: ignore
claims = kwargs.get("claims")
app = self._get_app(**kwargs)
flow = app.initiate_auth_code_flow(
scopes,
redirect_uri=redirect_uri,
prompt="select_account",
claims_challenge=claims,
login_hint=self._login_hint,
)
if "auth_uri" not in flow:
raise CredentialUnavailableError("Failed to begin authentication flow")

if not _open_browser(flow["auth_uri"]):
raise CredentialUnavailableError(message="Failed to open a browser")

# block until the server times out or receives the post-authentication redirect
response = server.wait_for_redirect()
if not response:
if within_dac.get():
raise CredentialUnavailableError(
message="Timed out after waiting {} seconds for the user to authenticate".format(self._timeout)
)

# redeem the authorization code for a token
return app.acquire_token_by_auth_code_flow(flow, response, scopes=scopes, claims_challenge=claims)

port = self._parsed_url.port if self._parsed_url else None

try:
result = app.acquire_token_interactive(
scopes=scopes,
login_hint=self._login_hint,
claims_challenge=claims,
timeout=self._timeout,
prompt="select_account",
port=port,
raise ClientAuthenticationError(
message="Timed out after waiting {} seconds for the user to authenticate".format(self._timeout)
)
except socket.error as ex:
raise CredentialUnavailableError(message="Couldn't start an HTTP server.") from ex
if "access_token" not in result and "error_description" in result:
if within_dac.get():
raise CredentialUnavailableError(message=result["error_description"])
raise ClientAuthenticationError(message=result.get("error_description"))
if "access_token" not in result:
if within_dac.get():
raise CredentialUnavailableError(message="Failed to authenticate user")
raise ClientAuthenticationError(message="Failed to authenticate user")

# base class will raise for other errors
return result
# redeem the authorization code for a token
return app.acquire_token_by_auth_code_flow(flow, response, scopes=scopes, claims_challenge=claims)


def _open_browser(url):
Expand Down