Skip to content

Commit aa78209

Browse files
committed
Add support for authenticating confidential client with request body params. Fix #55
1 parent 7ad5e00 commit aa78209

File tree

2 files changed

+90
-6
lines changed

2 files changed

+90
-6
lines changed

oauth2_provider/oauth2_validators.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323

2424

2525
class OAuth2Validator(RequestValidator):
26-
def authenticate_client(self, request, *args, **kwargs):
26+
def _authenticate_basic_auth(self, request):
2727
"""
28-
Check if client exists and it's authenticating itself as in rfc:`3.2.1`
28+
Authenticates with HTTP Basic Auth
2929
"""
3030
auth = request.headers.get('HTTP_AUTHORIZATION', None)
3131

3232
if not auth:
3333
return False
3434

3535
auth_type, auth_string = auth.split(' ')
36+
if auth_type != "Basic":
37+
return False
38+
3639
encoding = request.encoding or 'utf-8'
3740

3841
auth_string_decoded = base64.b64decode(auth_string).decode(encoding)
@@ -45,15 +48,51 @@ def authenticate_client(self, request, *args, **kwargs):
4548
except Application.DoesNotExist:
4649
return False
4750

51+
def _authenticate_request_body(self, request):
52+
"""
53+
Try to authenticate the client using client_id and client_secret parameters
54+
included in body.
55+
56+
Remember that this method is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize
57+
the HTTP Basic authentication scheme. See rfc:`2.3.1` for more details.
58+
"""
59+
client_id = request.client_id
60+
client_secret = request.client_secret
61+
62+
if not client_id:
63+
return False
64+
65+
try:
66+
request.client = Application.objects.get(client_id=client_id, client_secret=client_secret)
67+
return True
68+
69+
except Application.DoesNotExist:
70+
return False
71+
72+
def authenticate_client(self, request, *args, **kwargs):
73+
"""
74+
Check if client exists and it's authenticating itself as in rfc:`3.2.1`
75+
76+
First we try to authenticate with HTTP Basic Auth, and that is the PREFERRED authentication method.
77+
Whether this fails we support including the client credentials in the request-body, but
78+
this method is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize
79+
the HTTP Basic authentication scheme. See rfc:`2.3.1` for more details
80+
"""
81+
authenticated = self._authenticate_basic_auth(request)
82+
83+
if not authenticated:
84+
authenticated = self._authenticate_request_body(request)
85+
86+
return authenticated
87+
4888
def authenticate_client_id(self, client_id, request, *args, **kwargs):
4989
"""
5090
If we are here, the client did not authenticate itself as in rfc:`3.2.1` and we can proceed only if the client
5191
exists and it's not of type 'Confidential'. Also assign Application instance to request.client.
5292
"""
53-
client_secret = request.client_secret
5493

5594
try:
56-
request.client = request.client or Application.objects.get(client_id=client_id, client_secret=client_secret)
95+
request.client = Application.objects.get(client_id=client_id)
5796
return request.client.client_type != Application.CLIENT_CONFIDENTIAL
5897

5998
except Application.DoesNotExist:

oauth2_provider/tests/test_authorization_code.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
import base64
34
import json
45

56
from django.test import TestCase, RequestFactory
@@ -437,6 +438,51 @@ def test_basic_auth_bad_secret(self):
437438
response = self.client.post(reverse('oauth2_provider:token'), data=token_request_data, **auth_headers)
438439
self.assertEqual(response.status_code, 400)
439440

441+
def test_basic_auth_wrong_auth_type(self):
442+
"""
443+
Request an access token using basic authentication for client authentication
444+
"""
445+
self.client.login(username="test_user", password="123456")
446+
authorization_code = self.get_auth()
447+
448+
token_request_data = {
449+
'grant_type': 'authorization_code',
450+
'code': authorization_code,
451+
'redirect_uri': 'http://example.it'
452+
}
453+
454+
user_pass = '{0}:{1}'.format(self.application.client_id, self.application.client_secret)
455+
auth_string = base64.b64encode(user_pass.encode('utf-8'))
456+
auth_headers = {
457+
'HTTP_AUTHORIZATION': 'Wrong ' + auth_string.decode("utf-8"),
458+
}
459+
460+
response = self.client.post(reverse('oauth2_provider:token'), data=token_request_data, **auth_headers)
461+
self.assertEqual(response.status_code, 400)
462+
463+
def test_request_body_params(self):
464+
"""
465+
Request an access token using client_type: public
466+
"""
467+
self.client.login(username="test_user", password="123456")
468+
authorization_code = self.get_auth()
469+
470+
token_request_data = {
471+
'grant_type': 'authorization_code',
472+
'code': authorization_code,
473+
'redirect_uri': 'http://example.it',
474+
'client_id': self.application.client_id,
475+
'client_secret': self.application.client_secret,
476+
}
477+
478+
response = self.client.post(reverse('oauth2_provider:token'), data=token_request_data)
479+
self.assertEqual(response.status_code, 200)
480+
481+
content = json.loads(response.content.decode("utf-8"))
482+
self.assertEqual(content['token_type'], "Bearer")
483+
self.assertEqual(content['scope'], "read write")
484+
self.assertEqual(content['expires_in'], oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS)
485+
440486
def test_public(self):
441487
"""
442488
Request an access token using client_type: public
@@ -451,8 +497,7 @@ def test_public(self):
451497
'grant_type': 'authorization_code',
452498
'code': authorization_code,
453499
'redirect_uri': 'http://example.it',
454-
'client_id': self.application.client_id,
455-
'client_secret': self.application.client_secret,
500+
'client_id': self.application.client_id
456501
}
457502

458503
response = self.client.post(reverse('oauth2_provider:token'), data=token_request_data)

0 commit comments

Comments
 (0)