Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Use broker or browser login instead of device flow (#2612)
Browse files Browse the repository at this point in the history
Update CLI to attempt broker or browser-based authentication first; if you `Ctrl-C` to cancel it, you can fall back to device code login.

Also updated the MSAL dependency to latest version and pass `allow_broker=True` which will allow the use of Web Account Manager (WAM), if it is available.

Using browser auth requires the `http://localhost` redirect URI, and using the broker requires a special custom URI including the app ID (see code).
  • Loading branch information
Porges authored Nov 14, 2022
1 parent 04e114f commit ab0f365
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 157 deletions.
60 changes: 37 additions & 23 deletions src/cli/onefuzz/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def __init__(
self.config = config
self.token_cache: Optional[msal.SerializableTokenCache] = None
self.init_cache()
self.app: Optional[Any] = None
self.app: Optional[msal.ClientApplication] = None
self.token_expires = 0
self.load_config()
self.session = requests.Session()
Expand Down Expand Up @@ -197,7 +197,7 @@ def get_access_token(self) -> Any:
if self.client_secret:
return self.access_token_from_client_secret(scopes)

return self.device_login(scopes)
return self.do_login(scopes)

def access_token_from_client_secret(self, scopes: List[str]) -> Any:
if not self.app:
Expand Down Expand Up @@ -229,14 +229,16 @@ def access_token_from_client_secret(self, scopes: List[str]) -> Any:
)
return result

def device_login(self, scopes: List[str]) -> Any:
def do_login(self, scopes: List[str]) -> Any:
if not self.app:
self.app = msal.PublicClientApplication(
self.config.client_id,
authority=self.config.authority,
token_cache=self.token_cache,
allow_broker=True,
)

access_token = None
for scope in scopes:
accounts = self.app.get_accounts()
if accounts:
Expand All @@ -248,26 +250,38 @@ def device_login(self, scopes: List[str]) -> Any:

for scope in scopes:
LOGGER.info("Attempting interactive device login")
print("Please login", flush=True)

flow = self.app.initiate_device_flow(scopes=[scope])

check_msal_error(flow, ["user_code", "message"])
# setting the expiration time to allow us to retry the interactive login with a new scope
flow["expires_at"] = int(time.time()) + 90 # 90 seconds from now
print(flow["message"], flush=True)

access_token = self.app.acquire_token_by_device_flow(flow)
# AADSTS70016: OAuth 2.0 device flow error. Authorization is pending
# this happens when the intractive login request times out. This heppens when the login
# fails because of a scope mismatch.
if (
"error" in access_token
and "AADSTS70016" in access_token["error_description"]
):
LOGGER.warning(f"failed to get access token with scope {scope}")
continue
check_msal_error(access_token, ["access_token"])
try:
access_token = self.app.acquire_token_interactive(
scopes=[scope],
parent_window_handle=msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE,
)
check_msal_error(access_token, ["access_token"])
except KeyboardInterrupt:
result = input(
"\nInteractive login cancelled. Use device login (Y/n)? "
)
if result == "" or result.startswith("y") or result.startswith("Y"):
print("Falling back to device flow, please sign in:", flush=True)
flow = self.app.initiate_device_flow(scopes=[scope])

check_msal_error(flow, ["user_code", "message"])
# setting the expiration time to allow us to retry the interactive login with a new scope
flow["expires_at"] = int(time.time()) + 90 # 90 seconds from now
print(flow["message"], flush=True)

access_token = self.app.acquire_token_by_device_flow(flow)
# AADSTS70016: OAuth 2.0 device flow error. Authorization is pending
# this happens when the intractive login request times out. This heppens when the login
# fails because of a scope mismatch.
if (
"error" in access_token
and "AADSTS70016" in access_token["error_description"]
):
LOGGER.warning(f"failed to get access token with scope {scope}")
continue
check_msal_error(access_token, ["access_token"])
else:
continue

LOGGER.info("Interactive device authentication succeeded")
print("Login succeeded", flush=True)
Expand Down
4 changes: 2 additions & 2 deletions src/cli/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
msal==1.18.0b1
msal==1.20.0
requests~=2.27.1
jmespath~=0.10.0
semver~=2.13.0
Expand All @@ -12,7 +12,7 @@ azure-applicationinsights==0.1.0
tenacity==8.0.1
docstring_parser==0.8.1
azure-identity==1.10.0
azure-cli-core==2.40.0
azure-cli-core==2.42.0
# packaging is required but not specified by azure-cli-core
packaging==21.3
# urllib3[secure] needs to be specifically stated for azure-cli-core
Expand Down
Loading

0 comments on commit ab0f365

Please sign in to comment.