User verification
Probably the simplest case is when you only want to verify on the server-side who the currently signed-in user is, e.g. to load user-specific data/settings for them. For this you can use the most basic sign-in implementation, securely send the ID token to the server and use one of the Google API Client Libraries to verify the token and get user information from it.On the client side you wait for the sign-in success event to trigger, get the id_token from the authenticated user and send it to your server. You should always send the id_token via HTTPS for security reasons.
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Google Sign-in 2.0 - Basic Client</title> | |
<script src="https://apis.google.com/js/client:platform.js" async defer></script> | |
<meta name="google-signin-client_id" content="YOUR-CLIENT-ID.apps.googleusercontent.com"> | |
</head> | |
<body> | |
<div class="g-signin2" data-onsuccess="onSignIn"></div> | |
<script type="text/javascript"> | |
(function (global) { | |
global.onSignIn = function (user) { | |
var id_token = user.getAuthResponse().id_token; | |
// some function to send the id_token to your server | |
sendPostRequest('/verify', {id_token: id_token}) | |
}; | |
}(this)); | |
</script> | |
</body> | |
</html> |
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
#!/usr/bin/python | |
import json | |
import random | |
import string | |
from flask import Flask | |
from flask import make_response | |
from flask import request | |
import httplib2 | |
import oauth2client.client | |
from oauth2client.crypt import AppIdentityError | |
APPLICATION_NAME = 'Google Sign-in 2.0 - Basic Server' | |
app = Flask(__name__) | |
app.secret_key = ''.join(random.choice(string.ascii_uppercase + string.digits) | |
for x in xrange(32)) | |
CLIENT_ID = json.loads( | |
open('client_secrets.json', 'r').read())['web']['client_id'] | |
@app.route('/verify', methods=['POST']) | |
def verify(): | |
id_token = request.get_json().get('id_token', None) | |
try: | |
# Verify the ID token using the client library. | |
jwt = verify_id_token(id_token, CLIENT_ID) | |
user_id = jwt['sub'] | |
except AppIdentityError: | |
user_id = None | |
if user_id is None: | |
response = make_response('invalid token', 401) | |
return response | |
# Here you can get data relevant to user_id and return it | |
response = make_response('successfully verified', 200) | |
return reponse | |
if __name__ == '__main__': | |
app.debug = True | |
app.run(host='0.0.0.0', port=4567) |
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
{ | |
"iss": "accounts.google.com", | |
"sub": "112336147904981294875", | |
"azp": "YOUR-CLIENT-ID.apps.googleusercontent.com", | |
"email": "email@gmail.com", | |
"at_hash": "ABCHASJDKJAHJ1231w", | |
"email_verified": true, | |
"aud": "YOUR-CLIENT-ID.apps.googleusercontent.com", | |
"iat": 1429619207, | |
"exp": 1429622807, | |
"name": "Gerwin Sturm", | |
"picture": "https://lh3.googleusercontent.com/-khaIYLifQik/AAAAAAAAAAI/AAAAAAACclE/rspep_SceFo/s96-c/photo.jpg", | |
"given_name": "Gerwin", | |
"family_name": "Sturm", | |
"locale": "en" | |
} |
Optional server-side offline access
If you offer a web-service that will do something on behalf of the user while they are not online, I would recommend to make this an opt-in service after the user has signed-in.E.g. if your service offers sending news to a user via the Google Glass Mirror API, they could sign-in to your website first, pick the news categories they are interested in and then "flip a switch" to enable "offline access".
For this you would have the normal basic sign-in flow on the client-side. You can then use the ID token as before to check if the user already has offline access authorized (i.e. you have credentials stored for their user ID already). If there is no offline access yet you can display an extra button to go through the
grantOfflineAccess
flow to get a one-time code which can be exchanged for access and refresh tokens on the server side.
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Google Sign-in 2.0 - Optional Client</title> | |
<script src="https://apis.google.com/js/client:platform.js" async defer></script> | |
<meta name="google-signin-client_id" content="YOUR-CLIENT-ID.apps.googleusercontent.com"> | |
</head> | |
<body> | |
<div class="g-signin2" data-onsuccess="onSignIn"></div> | |
<button id="enable_offline_access" style="display: none">Enable Offline Access</button> | |
<script type="text/javascript"> | |
(function (global) { | |
global.onSignIn = function (user) { | |
var id_token = user.getAuthResponse().id_token; | |
// Some function to send the id_token to your server | |
sendPostRequest('/verify', {id_token: id_token}).then(function (response) { | |
if (!response.access_granted) { | |
global.document.getElementById('enable_offline_access').style.display = 'block'; | |
} | |
}); | |
}; | |
global.document.getElementById("enable_offline_access").onclick = function () { | |
// request one-time code | |
gapi.auth2.getAuthInstance().grantOfflineAccess({ | |
redirect_uri: 'postmessage', | |
scope: 'https://www.googleapis.com/auth/glass.timeline' | |
}).then(function (auth_response) { | |
// send one-time code to the server and wait for response | |
sendPostRequest('/authorize', {code: auth_response.code}).then(function (response) { | |
if (response.access_granted) { | |
global.document.getElementById('enable_offline_access').style.display = 'none'; | |
} | |
}); | |
}); | |
}; | |
}(this)); | |
</script> | |
</body> | |
</html> |
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
#!/usr/bin/python | |
import json | |
import random | |
import string | |
from flask import Flask | |
from flask import make_response | |
from flask import request | |
import httplib2 | |
import oauth2client.client | |
from oauth2client.crypt import AppIdentityError | |
APPLICATION_NAME = 'Google Sign-in 2.0 - Server' | |
app = Flask(__name__) | |
app.secret_key = ''.join(random.choice(string.ascii_uppercase + string.digits) | |
for x in xrange(32)) | |
CLIENT_ID = json.loads( | |
open('client_secrets.json', 'r').read())['web']['client_id'] | |
@app.route('/verify', methods=['POST']) | |
def verify(): | |
id_token = request.get_json().get('id_token', None) | |
try: | |
# Verify the ID token using the client library. | |
jwt = verify_id_token(id_token, CLIENT_ID) | |
user_id = jwt['sub'] | |
except AppIdentityError: | |
user_id = None | |
if user_id is None: | |
response = make_response('invalid token', 401) | |
return response | |
# try to retrieve previously stored credentials via some function | |
credentials = get_credentials(user_id) | |
response_data = {} | |
if credentials is None: | |
response_data['access_granted'] = False | |
else: | |
response_data['access_granted'] = True | |
response = make_response(json.dumps(response_data), 200) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
@app.route('/authorize', methods=['POST']) | |
def authorize(): | |
code = request.get_json().get('code', None) | |
try: | |
# Upgrade the authorization code into a credentials object | |
oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='') | |
oauth_flow.redirect_uri = 'postmessage' | |
credentials = oauth_flow.step2_exchange(code) | |
except FlowExchangeError: | |
response = make_response(json.dumps({'access_granted': False}), 401) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
user_id = credentials.id_token['sub'] | |
# store the credentials for this user via some function for later use | |
store_credentials(user_id, credentials) | |
response = make_response(json.dumps({'access_granted': True}), 200) | |
response.headers['Content-Type'] = 'application/json' | |
return response | |
if __name__ == '__main__': | |
app.debug = True | |
app.run(host='0.0.0.0', port=4567) |
grantOfflineAccess
will always cause a pop-up to show for the user requesting offline access. This is the only way to get a refresh token, also in case you lost a previous one.
Necessary server-side offline access
If your service won't work without offline access (would be curious to hear your use-cases here) and you don't want your users to go through two sign-in steps, things get a little bit more difficult on the client-side (while you can still use the sameserver.py
as above).
You can't use the default sign-in button for this, since this flow always runs without granting offline access. Instead you have to use your own custom button (make sure to create it following the branding guidlines) which calls grantOfflineAccess
.For "old" users that come to your website again calls
gapi.auth2.init
will initalize an immediate sign-in flow which you can catch with the isSignedIn
listener to check for existing credentials as before (just in case you lost them).
For "new" users the grantOfflineAccess
flow will return a code which you can exchange as above, and at the same time authenticate the user on the client side as well (calling your isSignedIn
listener).
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Google Sign-in 2.0 - Necessary Client</title> | |
<script src="https://apis.google.com/js/client:platform.js?onload=clientLoaded" async defer></script> | |
</head> | |
<body> | |
<button id="enable_offline_access">Enable Offline Access</button> | |
<script type="text/javascript"> | |
(function (global) { | |
global.clientLoaded = function () { | |
var authorizeProcessRunning = false; | |
global.gapi.load('auth2', function () { | |
var auth2 = gapi.auth2.init({ | |
client_id: 'YOUR-CLIENT-ID.apps.googleusercontent.com', | |
scope: 'profile https://www.googleapis.com/auth/glass.timeline' | |
}); | |
auth2.isSignedIn.listen(function (signedIn) { | |
/** | |
* This will be called after the auth library is initialized | |
* if the user has previously authenticated, or at the same time | |
* that grantOfflineAccess returns a code. | |
* We only want to verify the offline access for existing users | |
*/ | |
if (signedIn && !authorizeProcessRunning) { | |
sendPostRequest('/verify', {id_token: id_token}).then(function (response) { | |
if (response.access_granted) { | |
global.document.getElementById('enable_offline_access').style.display = 'none'; | |
} | |
}); | |
} | |
}); | |
auth2.then(function () { | |
global.document.getElementById("enable_offline_access").onclick = function () { | |
// request one-time code | |
authorizeProcessRunning = true; | |
gapi.auth2.getAuthInstance().grantOfflineAccess({ | |
redirect_uri: 'postmessage' | |
}).then(function (auth_response) { | |
// send one-time code to the server and wait for response | |
sendPostRequest('/authorize', {code: auth_response.code}).then(function (response) { | |
if (response.access_granted) { | |
global.document.getElementById('enable_offline_access').style.display = 'none'; | |
} | |
authorizeProcessRunning = false; | |
}); | |
}); | |
}; | |
}); | |
}); | |
}; | |
}(this)); | |
</script> | |
</body> | |
</html> |
I hope this answers some of the questions you have, feel free to comment if you have more :)