Created
October 19, 2015 22:14
-
-
Save adarsh0806/ef392dbb0906160e4263 to your computer and use it in GitHub Desktop.
Google OAuth gconnect with comments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def gconnect(): | |
""" Handles the Google+ sign-in process on the server side. | |
Server side function to handle the state-token and the one-time-code | |
send from the client callback function following the seven steps of the | |
Google+ sign-in flow. See the illustrated flow on | |
https://developers.google.com/+/web/signin/server-side-flow. | |
Returns: | |
When the sign-in was successful, a html response is sent to the client | |
signInCallback-function confirming the login. Otherwise, one of the | |
following responses is returned: | |
200 OK: The user is already connected. | |
401 Unauthorized: There is either a mismatch between the sent and | |
received state token, the received access token doesn't belong to | |
the intended user or the received client id doesn't match the web | |
apps client id. | |
500 Internal server error: The access token inside the received | |
credentials object is not a valid one. | |
Raises: | |
FlowExchangeError: The exchange of the one-time code for the | |
credentials object failed. | |
""" | |
# Confirm that the token the client sends to the server matches the | |
# state token that the server sends to the client. | |
# This roundship verification helps ensure that the user is making the | |
# request and and not a maliciousscript. | |
# Using the request.args.get-method, the code examines the state token | |
# passed in and compares it to the state of the login session. If thesse | |
# two do not match, a response message of an invalid state token is created | |
# and returned to the client. No further authentication will occur on the | |
# server side if there was a mismatch between these state token. | |
if request.args.get('state') != login_session['state']: | |
response = make_response(json.dumps('Invalid state parameter'), 401) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
# If the above statement is not true then I can proceed and collect the | |
# one-time code from the server with the request.data-function. | |
code = request.data | |
# 5) The Server tries to exchange the one-time code for an access_token and | |
# an id_token (credentials object). | |
# 6) When successful, Google returns the credentials object. Then the | |
# server is able to make its own API calls, which can be done while the | |
# user is offline. | |
try: | |
# Create an oauth_flow object and add clients secret key information | |
# to it. | |
oauth_flow = flow_from_clientsecrets( | |
'g_client_secrets.json', scope='') | |
# Postmessage specifies that this is the one-time-code flow that my | |
# server will be sending off. | |
oauth_flow.redirect_uri = 'postmessage' | |
# The exchange is initiated with the step2_exchange-function passing in | |
# the one-time code as input. | |
# The step2_exchange-function of the flow-class exchanges an | |
# authorization (one-time) code for an credentials object. | |
# If all goes well, the response from Google will be an object which | |
# is stored under the name credentials. | |
credentials = oauth_flow.step2_exchange(code) | |
# If an error happens along the way, then this FlowExchangeError is thrown | |
# and sends the response as an JSON-object. | |
except FlowExchangeError: | |
response = make_response(json.dumps( | |
'Failed to upgrade the authorization code.'), 401) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
# After the credentials object has been received. It has to be checked if | |
# there is a valid access token inside of it. | |
access_token = credentials.access_token | |
# If the token is appended to the following url, the Google API server can | |
# verify that this is a valid token for use. | |
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s' | |
% access_token) | |
# Create a JSON get-request containing the url and access-token and store | |
# the result of this request in a variable called result | |
h = httplib2.Http() | |
result = json.loads(h.request(url, 'GET')[1]) | |
# If there was an error in the access token info, send a 500 internal | |
# server error is send to the client. | |
if result.get('error') is not None: | |
response = make_response(json.dumps(result.get('error')), 500) | |
response.headers['Content-Type'] = 'application/json' | |
# If the above if-statement isn't true then the access token is working. | |
# Next, verify that the access token is used for the intended user. | |
# Grab the id of the token in my credentials object and compare it to the | |
# id returned by the google api server. If these two ids do not match, then | |
# I do not have the correct token and should return an error. | |
gplus_id = credentials.id_token['sub'] | |
if result['user_id'] != gplus_id: | |
response = make_response( | |
json.dumps("Token's user ID doesn't match given user ID."), 401) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
# Similary, if the client ids do not match, then my app is trying to use a | |
# client_id that doesn't belong to it. So I shouldn't allow for this. | |
# Verify that the access token is valid for this app. | |
if result['issued_to'] != CLIENT_ID: | |
response = make_response( | |
json.dumps("Token's client ID does not match."), 401) | |
print "Token's client ID does not match app's." | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
# Check if the user is already logged in | |
# ! Credentials shouldn't been stored in the session | |
# stored_credentials = login_session.get('credentials') | |
stored_credentials = login_session.get('access_token') | |
stored_gplus_id = login_session.get('gplus_id') | |
if stored_credentials is not None and gplus_id == stored_gplus_id: | |
response = make_response( | |
json.dumps('Current user is already connected.'), 200) | |
response.headers['Content-Type'] = 'application/json' | |
# So assuming that none of these if-statements were true, I have a valid | |
# access token and my user is successfully able to login to my server. | |
# In this user's login_session, the credentials and the gplus_id are stored | |
# to recall later (see check above). | |
login_session['provider'] = 'google' | |
login_session['access_token'] = credentials.access_token | |
login_session['gplus_id'] = gplus_id | |
# Use the google plus API to get some more information about the user. | |
# Here, a message is send off to the google API server with the access | |
# token requesting the user info allowed by the token scope and store it in | |
# an object called data. | |
userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo" | |
params = {'access_token': credentials.access_token, 'alt': 'json'} | |
answer = requests.get(userinfo_url, params=params) | |
data = json.loads(answer.text) | |
# Data should have all of the values listed on | |
# https://developers.google.com/+/api/openidconnect/getOpenIdConnect#response | |
# filled in, so long as the user specified them in their account. In the | |
# following, the users name, picture and e-mail address are stored in the | |
# login session. | |
login_session['username'] = data["name"] | |
login_session['picture'] = data["picture"] | |
login_session['email'] = data["email"] | |
# If user doesn't exist, make a new one. | |
user_id = getUserID(login_session['email']) | |
if not user_id: | |
user_id = createUser(login_session) | |
login_session['user_id'] = user_id | |
# 7) If the above worked, a html response is returned confirming the login | |
# to the Client. | |
output = '' | |
output += '<h1>Welcome, ' | |
output += login_session['username'] | |
output += '!</h1>' | |
output += '<img src="' | |
output += login_session['picture'] | |
output += '" style = "width: 300px; height: 300px; border-radius: 150px;' | |
output += '-webkit-border-radius: 150px;-moz-border-radius: 150px;">' | |
flash("You are now logged in as %s" % login_session['username']) | |
return output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment