Support authentication using Authorization header

According to RFC 6749, section 2.3.1:

> Including the client credentials in the request-body using the two
> parameters is NOT RECOMMENDED and SHOULD be limited to clients unable
> to directly utilize the HTTP Basic authentication scheme (or other
> password-based HTTP authentication schemes).

This changeset adds an optional parameter, `authorization_header` that
makes it easier to use this client library with OAuth2 providers that
support client authentication via the HTTP Authorization header
instead of in the request body.
This commit is contained in:
Brendan McCollam
2015-06-23 16:29:49 -05:00
parent c5eb9f14d4
commit 72b240453a
2 changed files with 37 additions and 3 deletions

View File

@@ -1784,7 +1784,9 @@ class OAuth2WebServerFlow(Flow):
"""
@util.positional(4)
def __init__(self, client_id, client_secret, scope,
def __init__(self, client_id,
client_secret=None,
scope=None,
redirect_uri=None,
user_agent=None,
auth_uri=GOOGLE_AUTH_URI,
@@ -1792,6 +1794,7 @@ class OAuth2WebServerFlow(Flow):
revoke_uri=GOOGLE_REVOKE_URI,
login_hint=None,
device_uri=GOOGLE_DEVICE_URI,
authorization_header=None,
**kwargs):
"""Constructor for OAuth2WebServerFlow.
@@ -1819,9 +1822,14 @@ class OAuth2WebServerFlow(Flow):
proper multi-login session, thereby simplifying the login flow.
device_uri: string, URI for device authorization endpoint. For convenience
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
authorization_header: string, For use with OAuth 2.0 providers that
require a client to authenticate using a header value instead of passing
client_secret in the POST body.
**kwargs: dict, The keyword arguments are all optional and required
parameters for the OAuth calls.
"""
if scope is None:
raise TypeError("The value of scope must not be None")
self.client_id = client_id
self.client_secret = client_secret
self.scope = util.scopes_to_string(scope)
@@ -1832,6 +1840,7 @@ class OAuth2WebServerFlow(Flow):
self.token_uri = token_uri
self.revoke_uri = revoke_uri
self.device_uri = device_uri
self.authorization_header = authorization_header
self.params = {
'access_type': 'offline',
'response_type': 'code',
@@ -1957,10 +1966,11 @@ class OAuth2WebServerFlow(Flow):
post_data = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'code': code,
'scope': self.scope,
}
if self.client_secret is not None:
post_data['client_secret'] = self.client_secret
if device_flow_info is not None:
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
else:
@@ -1970,7 +1980,8 @@ class OAuth2WebServerFlow(Flow):
headers = {
'content-type': 'application/x-www-form-urlencoded',
}
if self.authorization_header is not None:
headers['Authorization'] = self.authorization_header
if self.user_agent is not None:
headers['user-agent'] = self.user_agent

View File

@@ -1027,6 +1027,29 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
request_code = urllib.parse.parse_qs(http.requests[0]['body'])['code'][0]
self.assertEqual(code, request_code)
def test_exchange_using_authorization_header(self):
auth_header = 'Basic Y2xpZW50X2lkKzE6c2VjcmV0KzE=',
flow = OAuth2WebServerFlow(
client_id='client_id+1',
authorization_header=auth_header,
scope='foo',
redirect_uri=OOB_CALLBACK_URN,
user_agent='unittest-sample/1.0',
revoke_uri='dummy_revoke_uri',
)
http = HttpMockSequence([
({'status': '200'}, b'access_token=SlAV32hkKG'),
])
credentials = flow.step2_exchange('some random code', http=http)
self.assertEqual('SlAV32hkKG', credentials.access_token)
test_request = http.requests[0]
# Did we pass the Authorization header?
self.assertEqual(test_request['headers']['Authorization'], auth_header)
# Did we omit client_secret from POST body?
self.assertNotIn('client_secret', test_request['body'])
def test_urlencoded_exchange_success(self):
http = HttpMockSequence([
({'status': '200'}, b'access_token=SlAV32hkKG&expires_in=3600'),