3
3
# Licensed under the MIT License.
4
4
# ------------------------------------
5
5
import socket
6
- import uuid
7
6
import webbrowser
8
7
9
8
from six .moves .urllib_parse import urlparse
21
20
22
21
if TYPE_CHECKING :
23
22
# pylint:disable=unused-import
24
- from typing import Any , List , Mapping
23
+ from typing import Any
25
24
26
25
27
26
class InteractiveBrowserCredential (InteractiveCredential ):
28
27
"""Opens a browser to interactively authenticate a user.
29
28
30
29
:func:`~get_token` opens a browser to a login URL provided by Azure Active Directory and authenticates a user
31
- there with the authorization code flow. Azure Active Directory documentation describes this flow in more detail:
30
+ there with the authorization code flow, using PKCE (Proof Key for Code Exchange) internally to protect the code.
31
+ Azure Active Directory documentation describes the authentication flow in more detail:
32
32
https://docs.microsoft.com/azure/active-directory/develop/v1-protocols-oauth-code
33
33
34
34
:keyword str authority: Authority of an Azure Active Directory endpoint, for example 'login.microsoftonline.com',
@@ -94,14 +94,15 @@ def _request_token(self, *scopes, **kwargs):
94
94
95
95
# get the url the user must visit to authenticate
96
96
scopes = list (scopes ) # type: ignore
97
- request_state = str ( uuid . uuid4 () )
97
+ claims = kwargs . get ( "claims" )
98
98
app = self ._get_app ()
99
- auth_url = app .get_authorization_request_url (
100
- scopes , redirect_uri = redirect_uri , state = request_state , prompt = "select_account"
99
+ flow = app .initiate_auth_code_flow (
100
+ scopes , redirect_uri = redirect_uri , prompt = "select_account" , claims_challenge = claims
101
101
)
102
+ if "auth_uri" not in flow :
103
+ raise CredentialUnavailableError ("Failed to begin authentication flow" )
102
104
103
- # open browser to that url
104
- if not webbrowser .open (auth_url ):
105
+ if not webbrowser .open (flow ["auth_uri" ]):
105
106
raise CredentialUnavailableError (message = "Failed to open a browser" )
106
107
107
108
# block until the server times out or receives the post-authentication redirect
@@ -112,32 +113,4 @@ def _request_token(self, *scopes, **kwargs):
112
113
)
113
114
114
115
# redeem the authorization code for a token
115
- code = self ._parse_response (request_state , response )
116
- return app .acquire_token_by_authorization_code (
117
- code , scopes = scopes , redirect_uri = redirect_uri , claims_challenge = kwargs .get ("claims" )
118
- )
119
-
120
- @staticmethod
121
- def _parse_response (request_state , response ):
122
- # type: (str, Mapping[str, Any]) -> List[str]
123
- """Validates ``response`` and returns the authorization code it contains, if authentication succeeded.
124
-
125
- Raises :class:`azure.core.exceptions.ClientAuthenticationError`, if authentication failed or ``response`` is
126
- malformed.
127
- """
128
-
129
- if "error" in response :
130
- message = "Authentication failed: {}" .format (response .get ("error_description" ) or response ["error" ])
131
- raise ClientAuthenticationError (message = message )
132
- if "code" not in response :
133
- # a response with no error or code is malformed; we don't know what to do with it
134
- message = "Authentication server didn't send an authorization code"
135
- raise ClientAuthenticationError (message = message )
136
-
137
- # response must include the state sent in the auth request
138
- if "state" not in response :
139
- raise ClientAuthenticationError (message = "Authentication response doesn't include OAuth state" )
140
- if response ["state" ][0 ] != request_state :
141
- raise ClientAuthenticationError (message = "Authentication response's OAuth state doesn't match the request's" )
142
-
143
- return response ["code" ]
116
+ return app .acquire_token_by_auth_code_flow (flow , response , scopes = scopes , claims_challenge = claims )
0 commit comments