Social SignIn in a mobile application – Designing the backend in Python
Introduction
There are tons of mobile applications available – one for every software application that you can think of, and even more being published every day. Almost all of these apps implement some form of user sign up to understand and enable their users to perform adequate tasks. While there is a visible benefit that this brings in, the downside is that a user must remember their credentials for all the apps he or she uses. Well, that is a lot of username and password combinations!
One method to help alleviate this cognitive load on the user and improve app experience is to allow for users to sign-up/ sign-in with the credentials they already use with trusted social network sites like Google+ and Facebook for example. Developers need to provide the option for users to either use custom credentials or get authenticated on the app via these authentication providers (will use this term going forward here for the social network sites like Google+ and Facebook)
There is a lot of documentation made available online by these authentication providers on how an app developer - be it native, hybrid or web app – can integrate this authentication mechanism. Most of the text, however, focus on the front-end integration and does not educate a developer on how to develop their backend and/or service layer to integrate the standard sign-in flow (username/password) with social authentication.
Before we move forward, if all of the above still has a few doubts unanswered, I would urge you to read through this wonderful article on how authentication mechanisms have matured from basic to OAuth2 over time and the providers available today. The purpose of my article here is to help you design your backend to allow for social login in your android application with Google as the authentication provider.
First, let us look at the flows below for basic authentication and then OAuth2 flow with Google as the authentication provider.
Now, the problem is how do you have both flows in a single app and integrate both flows into a single one for code maintainability and a consistent user experience.
There are primarily 2 components to focus when designing the authentication architecture.
Part 1: Application token generation
Once the user signs in successfully, the backend flow will start with one of the two methods below followed by the set of common steps.
Username/Password Sign In
- In this case, the authentication happens on the application server and the token needs to be generated immediately after (the information coming up).
Google Sign In
- Pass the token provided by Google back to the server.
- Verify the integrity of the token on the backend against the Google Servers. This is important to mitigate man-in-the-middle-attacks.
The code below from https://developers.google.com/identity/sign-in/android/backend-auth shows you how to go about it.
from google.oauth2 import id_token
from google.auth.transport import requests
# (Receive token by HTTPS POST)
# ...
try:
# Specify the CLIENT_ID of the app that accesses the backend:
idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
# Or, if multiple clients access the backend server:
# idinfo = id_token.verify_oauth2_token(token, requests.Request())
# if idinfo['aud'] not in [CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]:
# raise ValueError('Could not verify audience.')
if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
raise ValueError('Wrong issuer.')
# If auth request is from a G Suite domain:
# if idinfo['hd'] != GSUITE_DOMAIN_NAME:
# raise ValueError('Wrong hosted domain.')
# ID token is valid. Get the user's Google Account ID from the decoded token.
userid = idinfo['sub']
except ValueError:
# Invalid token
pass
Common steps: Application Token Generation
Once either of the above two scenarios is complete, the following steps must be undertaken
- A new application token identifying the user needs to be generated by the application server. This token should ideally contain
a. The username
b. Timestamp (iat) and a validity period/time(exp).
c. If the scenario is of social login the same iat and exp as of the google id token may be set here. That way we may know that both tokens will expire at the same time. - Encode the user information with an application secret key to generate the token – called a JSON Web Token (JWT).
- Now store this application generated JWT, along with the user details and the google id token (if social login has been used) in a database. We will see later why this is required.
- Return this application token in the response back to the frontend. The front-end client will then ensure to save this token and send it with every subsequent request – to get user and application data related to the user.
You will find the code below to generate the token and verify its authenticity
import jwt
import datetime
def generate_auth_token(user_id, email_id=''):
"""
Generates the Auth Token
Must include the exp, iat and sub fields
May include any additional 'non secret' information
:return: string
"""
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=100), #expiry at
'iat': datetime.datetime.utcnow(), #issued at
'sub': user_id, #subject
'email' : email_id #additional fields
}
return jwt.encode(
payload,
'SECRET_KEY',
algorithm='HS256'
)
except Exception as e:
return e
token = encode_auth_token('nilavghosh', 'nilavghosh@gmail.com')
print(token)
b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuaWxhdmdob3NoIiwiaWF0IjoxNTM2OTM3MTE2LCJleHAiOjE1MzY5MzcyMTYsImVtYWlsIjoibmlsYXZnaG9zaEBnbWFpbC5jb20ifQ.5Y50vjwf0h9B7k1s0pGzM3-IMrLgrJp9zFmf8mSh7I8'
The database token document structure for Mongo DB:
"nilavghosh" : {
"is_valid": true,
"exp": 1536936980,
"iat": 1536936880,
"token" : "be7a35aa41b33f93ee98658083b319944cae2e8d52e143ee",
"social_token" : "eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ5NjQ4ZTAzMmNhYz",
"is_social" : true,
"email" : "nilavghosh@gmail.com"
}
Part 2: Checking token validity on next requests
When any requests are sent from the client, they must send across the application token – generally as part of the ‘Authorization’ header. The validity of the token must be checked whenever a request from the app/client is fired. If the token is invalid or expired then a 401 response must be sent back and the user must go through the sign in flow again. The thing to note is that a database call is not required as a JWT token may be decoded in code using the app secret key. The expiry date/time may then be checked against the ‘exp’ field. The code for the same is in the next section.
Checking validity for application token:
print(jwt.decode(token, 'SECRET_KEY', algorithms=['HS256']))
{'email': 'nilavghosh@gmail.com',
'exp': 1536936980,
'iat': 1536936880,
'sub': 'nilavghosh'}
The code below shows how to create a reusable token authorization function as a python decorator.
import functools
from flask import abort
import jwt
def authorize(f):
@@functools.wraps((f)
def check_token_validity(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
token = request.headers['Authorization'].encode('ascii','ignore')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(*args, **kws)
return check_token_validity
@app.route('/api/changeinfo', methods=['POST'])
@authorize
def get_user_data():
#do something
pass
The thing to note is that since the expiry time of the app token is set to the expiry time of the Google token (in the case of social sign-in) you may be sure that both the tokens will expire at the same time requiring the user to sign in again.
And that brings me to the end of this article. There are some other considerations which I have listed below which may be covered in a separate article.
- Ensuring long-lived tokens so that the user doesn’t need to sign in too often
- Enabling server-side flow for Google access and refresh tokens so that the backend may automatically refresh google tokens on behalf of the user.
- Storage of tokens securely on the client.
Hi Nilav Da, could you please share the source code ?
Do u have source code example for this implementation?
Hi Pavel, yes. drop me a note on nilavghosh@gmail.com and will share the code.
Thank you for the great article. Could you wrtie something how to protect access to server API to users who logged in via social account?