Skip to content

Commit 6fbf507

Browse files
authored
Snippets for Session Management (#148)
* Moved token generation/validation code to new helper module * Basic session cookie support (without tests) * Separated token generation and verification into two classes * Added unit tests for session management * Fixing a lint error * Added integration tests * Handling article in error messages * Fixed a lint error * Updated changelog * Added snippets for auth session management * Updated snippets * Added some comments * Merged with master; Updated CHANGELOG for #150 * Minor improvements to samples
1 parent dbd8da5 commit 6fbf507

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
session cookie given a valid ID token.
55
- [added] A new `verify_session_cookie()` method for verifying a given
66
cookie string is valid.
7+
- [added] `auth` module now caches the public key certificates used to
8+
verify ID tokens and sessions cookies. This enables the SDK to avoid
9+
making a network call everytime a credential needs to be verified.
710
- [added] Added the `mutable_content` optional field to the `messaging.Aps`
811
type.
912
- [added] Added support for specifying arbitrary custom key-value

snippets/auth/index.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import datetime
1516
import sys
17+
import time
18+
1619
# [START import_sdk]
1720
import firebase_admin
1821
# [END import_sdk]
@@ -289,6 +292,120 @@ def list_all_users():
289292
print 'User: ' + user.uid
290293
# [END list_all_users]
291294

295+
def create_session_cookie(flask, app):
296+
# [START session_login]
297+
@app.route('/sessionLogin', methods=['POST'])
298+
def session_login():
299+
# Get the ID token sent by the client
300+
id_token = flask.request.json['idToken']
301+
# Set session expiration to 5 days.
302+
expires_in = datetime.timedelta(days=5)
303+
try:
304+
# Create the session cookie. This will also verify the ID token in the process.
305+
# The session cookie will have the same claims as the ID token.
306+
session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
307+
response = flask.jsonify({'status': 'success'})
308+
# Set cookie policy for session cookie.
309+
expires = datetime.datetime.now() + expires_in
310+
response.set_cookie(
311+
'session', session_cookie, expires=expires, httponly=True, secure=True)
312+
return response
313+
except auth.AuthError:
314+
return flask.abort(401, 'Failed to create a session cookie')
315+
# [END session_login]
316+
317+
def check_auth_time(id_token, flask):
318+
# [START check_auth_time]
319+
# To ensure that cookies are set only on recently signed in users, check auth_time in
320+
# ID token before creating a cookie.
321+
try:
322+
decoded_claims = auth.verify_id_token(id_token)
323+
# Only process if the user signed in within the last 5 minutes.
324+
if time.time() - decoded_claims['auth_time'] < 5 * 60:
325+
expires_in = datetime.timedelta(days=5)
326+
expires = datetime.datetime.now() + expires_in
327+
session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
328+
response = flask.jsonify({'status': 'success'})
329+
response.set_cookie(
330+
'session', session_cookie, expires=expires, httponly=True, secure=True)
331+
return response
332+
# User did not sign in recently. To guard against ID token theft, require
333+
# re-authentication.
334+
return flask.abort(401, 'Recent sign in required')
335+
except ValueError:
336+
return flask.abort(401, 'Invalid ID token')
337+
except auth.AuthError:
338+
return flask.abort(401, 'Failed to create a session cookie')
339+
# [END check_auth_time]
340+
341+
def verfy_session_cookie(app, flask):
342+
def serve_content_for_user(decoded_claims):
343+
print 'Serving content with claims:', decoded_claims
344+
return flask.jsonify({'status': 'success'})
345+
346+
# [START session_verify]
347+
@app.route('/profile', methods=['POST'])
348+
def access_restricted_content():
349+
session_cookie = flask.request.cookies.get('session')
350+
# Verify the session cookie. In this case an additional check is added to detect
351+
# if the user's Firebase session was revoked, user deleted/disabled, etc.
352+
try:
353+
decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
354+
return serve_content_for_user(decoded_claims)
355+
except ValueError:
356+
# Session cookie is unavailable or invalid. Force user to login.
357+
return flask.redirect('/login')
358+
except auth.AuthError:
359+
# Session revoked. Force user to login.
360+
return flask.redirect('/login')
361+
# [END session_verify]
362+
363+
def check_permissions(session_cookie, flask):
364+
def serve_content_for_admin(decoded_claims):
365+
print 'Serving content with claims:', decoded_claims
366+
return flask.jsonify({'status': 'success'})
367+
368+
# [START session_verify_with_permission_check]
369+
try:
370+
decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
371+
# Check custom claims to confirm user is an admin.
372+
if decoded_claims.get('admin') is True:
373+
return serve_content_for_admin(decoded_claims)
374+
else:
375+
return flask.abort(401, 'Insufficient permissions')
376+
except ValueError:
377+
# Session cookie is unavailable or invalid. Force user to login.
378+
return flask.redirect('/login')
379+
except auth.AuthError:
380+
# Session revoked. Force user to login.
381+
return flask.redirect('/login')
382+
# [END session_verify_with_permission_check]
383+
384+
def clear_session_cookie(app, flask):
385+
# [START session_clear]
386+
@app.route('/sessionLogout', methods=['POST'])
387+
def session_logout():
388+
response = flask.make_response(flask.redirect('/login'))
389+
response.set_cookie('session', expires=0)
390+
return response
391+
# [END session_clear]
392+
393+
def clear_session_cookie_and_revoke(app, flask):
394+
# [START session_clear_and_revoke]
395+
@app.route('/sessionLogout', methods=['POST'])
396+
def session_logout():
397+
session_cookie = flask.request.cookies.get('session')
398+
try:
399+
decoded_claims = auth.verify_session_cookie(session_cookie)
400+
auth.revoke_refresh_tokens(decoded_claims['sub'])
401+
response = flask.make_response(flask.redirect('/login'))
402+
response.set_cookie('session', expires=0)
403+
return response
404+
except ValueError:
405+
return flask.redirect('/login')
406+
# [END session_clear_and_revoke]
407+
408+
292409
initialize_sdk_with_service_account()
293410
initialize_sdk_with_application_default()
294411
#initialize_sdk_with_refresh_token()

0 commit comments

Comments
 (0)