Skip to content

Commit 0bcead3

Browse files
feat(auth): sign in with facebook
1 parent 04c8e20 commit 0bcead3

File tree

1 file changed

+46
-14
lines changed

1 file changed

+46
-14
lines changed

firebase/auth/__init__.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414

1515
import json
1616
import math
17+
import pkce
18+
import random
1719
import datetime
1820
import python_jwt as jwt
1921
import jwcrypto.jwk as jwk
22+
from hashlib import sha256
2023
from urllib.parse import parse_qs
2124
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat
2225

@@ -50,6 +53,8 @@ def __init__(self, api_key, credentials, requests, client_secret=None):
5053

5154
self.provider_id = None
5255
self.session_id = None
56+
self.__code_verifier = None
57+
self.__nonce = None
5358

5459
if client_secret:
5560
self.client_secret = _load_client_secret(client_secret)
@@ -63,6 +68,15 @@ def authenticate_login_with_google(self):
6368
"""
6469
return self.create_authentication_uri('google.com')
6570

71+
def authenticate_login_with_facebook(self):
72+
""" Redirect the user to Facebook's OAuth 2.0 server to
73+
initiate the authentication and authorization process.
74+
75+
:return: Facebook Sign In URL
76+
:rtype: str
77+
"""
78+
return self.create_authentication_uri('facebook.com')
79+
6680
def create_authentication_uri(self, provider_id):
6781
""" Creates an authentication URI for the given social
6882
provider.
@@ -91,17 +105,22 @@ def create_authentication_uri(self, provider_id):
91105
request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key={0}".format(self.api_key)
92106

93107
data = {
94-
"authFlowType": 'CODE_FLOW',
95108
"clientId": self.client_secret['client_id'],
96109
"providerId": provider_id,
97110
"continueUri": self.client_secret['redirect_uris'][0],
98-
"customParameter": {
99-
"access_type": 'offline',
100-
"prompt": 'select_account',
101-
"include_granted_scopes": 'true',
102-
}
103111
}
104112

113+
self.__nonce = "".join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") for _ in range(20))
114+
115+
if provider_id == 'google.com':
116+
data['authFlowType'] = 'CODE_FLOW'
117+
data['customParameter'] = {"access_type": 'offline', "prompt": 'select_account', "include_granted_scopes": 'true', "nonce": self.__nonce}
118+
119+
if provider_id == 'facebook.com':
120+
self.__code_verifier, code_challenge = pkce.generate_pkce_pair()
121+
data['oauthScope'] = 'openid'
122+
data['customParameter'] = {"code_challenge": code_challenge, "code_challenge_method": 'S256', "nonce": sha256(self.__nonce.encode('utf')).hexdigest()}
123+
105124
headers = {"content-type": "application/json; charset=UTF-8"}
106125
request_object = self.requests.post(request_ref, headers=headers, json=data)
107126

@@ -221,7 +240,7 @@ def sign_in_with_custom_token(self, token):
221240
:rtype: dict
222241
"""
223242

224-
request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key={0}".format(self.api_key) # noqa
243+
request_ref = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key={0}".format(self.api_key) # noqa
225244

226245
headers = {"content-type": "application/json; charset=UTF-8"}
227246
data = json.dumps({"returnSecureToken": True, "token": token})
@@ -447,7 +466,7 @@ def sign_in_with_oauth_credential(self, oauth2callback_url):
447466

448467
token = self._token_from_auth_url(oauth2callback_url)
449468
data = {
450-
'postBody': 'providerId={0}&{1}={2}'.format(self.provider_id, token['type'], token['value']),
469+
'postBody': f"providerId={self.provider_id}&{token['type']}={token['value']}&nonce={self.__nonce}",
451470
'autoCreate': 'true',
452471
'requestUri': self.client_secret['redirect_uris'][0],
453472
'sessionId': self.session_id,
@@ -476,18 +495,22 @@ def _token_from_auth_url(self, url):
476495
:rtype: dict
477496
"""
478497

479-
request_ref = 'https://www.googleapis.com/oauth2/v4/token'
498+
request_ref = _token_host(self.provider_id)
480499

481500
auth_url_values = parse_qs(url[url.index('?') + 1:])
482501

483502
data = {
484503
'client_id': self.client_secret['client_id'],
485504
'client_secret': self.client_secret['client_secret'],
486505
'code': auth_url_values['code'][0],
487-
'grant_type': 'authorization_code',
488506
'redirect_uri': self.client_secret['redirect_uris'][0],
489507
}
490508

509+
if self.provider_id == 'google.com':
510+
data['grant_type'] = 'authorization_code'
511+
elif self.provider_id == 'facebook.com':
512+
data['code_verifier'] = self.__code_verifier
513+
491514
headers = {"content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
492515
request_object = self.requests.post(request_ref, headers=headers, data=data)
493516

@@ -557,7 +580,8 @@ def _load_client_secret(secret):
557580

558581
# Google client secrets are stored within 'web' key
559582
# We will remove the key, and replace it with the dict type value of it
560-
secret = secret['web']
583+
if secret.get('web'):
584+
secret = secret['web']
561585

562586
return secret
563587

@@ -574,7 +598,15 @@ def _token_expire_time(user):
574598
:return: Token dictionary with an ``expiresAt`` key.
575599
:rtype: dict
576600
"""
577-
578-
user['expiresAt'] = math.floor(datetime.datetime.today().timestamp() + int(user.get('expiresIn')) - 60)
579-
601+
602+
user['expiresAt'] = math.floor(datetime.datetime.today().timestamp() + int(user.get('expiresIn', 3600)) - 60)
603+
580604
return user
605+
606+
607+
def _token_host(provider):
608+
if provider == 'google.com':
609+
return 'https://www.googleapis.com/oauth2/v4/token'
610+
611+
elif provider == 'facebook.com':
612+
return 'https://graph.facebook.com/v14.0/oauth/access_token'

0 commit comments

Comments
 (0)