This post explains how to add Google Oauth2 login in a Flask web app using the requests-oauthlib package for OAuth 2.0 and flask-sqlalchemy.
To get started, first we have to create a project in Google Developers Console to get client key and secret.
Creating a Google project
- First go to Google Developers Console. Sign in using your Google credentials if you haven’t already. There will be a list of projects(if you have previously created any).
- Click on Create Project to create a new project.
- Provide a project name in the dialog box and press enter. For explanation purposes, lets say the project name is test-project-123xyz.
test-project-123xyz
will appear in the list of projects after creation. - Now go to the project page. Click
APIs and Auth -> Credentials
in the sidebar. Then goto theOAuth Consent Screen
. Provide theProduct Name
(you can also provide other details but they are optional).Product Name
is what users see when they are logging into your application using Google. - Now click on the
Credentials
part of the same page. Then click onAdd Credentials
and then selectOAuth 2.0 client ID
. - Select
Application Type
as Web Application, Provide aName
,Authorized Javascript origins
andAuthorized redirect URIs
and click onCreate
. During development, we will uselocalhost
as our URL. Later, for production, we can add our original URL. Theredirect URIs
is important here as this is the URL the users will be redirected to after Google Login. Make sure that all the urls usehttps
protocol asOAuth2
supports onlyhttps
. - After the above step, you will be presented with a dialog box having your
client ID
andclient secret
. Copy both the strings and save in a text file as we will be needing these later.
Creating a User table in Database
We will be using flask-sqlalchemy to handle DB operations. This is what our
User
table looks like.
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True, nullable=False)
name = db.Column(db.String(100), nullable=True)
avatar = db.Column(db.String(200))
active = db.Column(db.Boolean, default=False)
tokens = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow())
The tokens
column stores the access and refresh tokens JSON, dumped as string.
Creating configuration for our app.
If using flask-login
to manage user sessions, we can check whether a user is logged in or not. If not logged in, we redirect the user to a login page that contains the link to Google login. Lets create a config.py
that has our Google OAuth credentials and our app configuration.
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Auth:
CLIENT_ID = ('688061596571-3c13n0uho6qe34hjqj2apincmqk86ddj'
'.apps.googleusercontent.com')
CLIENT_SECRET = 'JXf7Ic_jfCam1S7lBJalDyPZ'
REDIRECT_URI = 'https://localhost:5000/gCallback'
AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
USER_INFO = 'https://www.googleapis.com/userinfo/v2/me'
class Config:
APP_NAME = "Test Google Login"
SECRET_KEY = os.environ.get("SECRET_KEY") or "somethingsecret"
class DevConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, "test.db")
class ProdConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, "prod.db")
config = {
"dev": DevConfig,
"prod": ProdConfig,
"default": DevConfig
}
Here,
REDIRECT_URI
is what we set in Google Developers Console,
AUTH_URI
is where the user is taken to for Google login,
TOKEN_URI
is used to exchange a temporary token for an access_token
and
USER_INFO
is the URL used for retrieving user information like name, email, etc after successful authentication.
SCOPE
is the types of user information that we will be accessing after the user authenticates our app. Google OAuth2 Playground has a list of scopes that can be added.
Implementing the URL routes for login and callback
After the configuration is done, we have to create a Flask
app, load configurations and finally define our routes.
app = Flask(__name__)
app.config.from_object(config['dev'])
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = "login"
login_manager.session_protection = "strong"
requests_oauthlib.OAuth2Session
helper:
We create a helper function get_google_auth
that we will use to create OAuth2Session
object based on the arguments provided.
def get_google_auth(state=None, token=None):
if token:
return OAuth2Session(Auth.CLIENT_ID, token=token)
if state:
return OAuth2Session(
Auth.CLIENT_ID,
state=state,
redirect_uri=Auth.REDIRECT_URI)
oauth = OAuth2Session(
Auth.CLIENT_ID,
redirect_uri=Auth.REDIRECT_URI,
scope=Auth.SCOPE)
return oauth
- When none of the parameters are provided, e.g.
google = get_google_auth()
, it creates a new OAuth2Session
with a new state.
- If
state
is provided, that means we have to get a token
.
- If
token
is provided, that means we only have to get an access_token
and this is the final step.
Root URL:
@app.route('/')
@login_required
def index():
return render_template('index.html')
This route is only served to logged in user. If a user is not logged in, they are redirected to login
route as set previously using login_manager.login_view = "login"
.
Login URL:
@app.route('/login')
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
google = get_google_auth()
auth_url, state = google.authorization_url(
Auth.AUTH_URI, access_type='offline')
session['oauth_state'] = state
return render_template('login.html', auth_url=auth_url)
Here we save the value of state
in cookie using session['oauth_state'] = state
to be used later.
Callback URL:
Here, the route gCallback
must be the same as we mentioned in our project page in Google Developers Console.
@app.route('/gCallback')
def callback():
# Redirect user to home page if already logged in.
if current_user is not None and current_user.is_authenticated:
return redirect(url_for('index'))
if 'error' in request.args:
if request.args.get('error') == 'access_denied':
return 'You denied access.'
return 'Error encountered.'
if 'code' not in request.args and 'state' not in request.args:
return redirect(url_for('login'))
else:
# Execution reaches here when user has
# successfully authenticated our app.
google = get_google_auth(state=session['oauth_state'])
try:
token = google.fetch_token(
Auth.TOKEN_URI,
client_secret=Auth.CLIENT_SECRET,
authorization_response=request.url)
except HTTPError:
return 'HTTPError occurred.'
google = get_google_auth(token=token)
resp = google.get(Auth.USER_INFO)
if resp.status_code == 200:
user_data = resp.json()
email = user_data['email']
user = User.query.filter_by(email=email).first()
if user is None:
user = User()
user.email = email
user.name = user_data['name']
print(token)
user.tokens = json.dumps(token)
user.avatar = user_data['picture']
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for('index'))
return 'Could not fetch your information.'
In the above code,
- We check if a user is already logged in. If yes, we then redirect them to the home page.
- Then we check if the url has an
error
query parameter. This check is done to handle cases where a user after going to the Google login page, denies access. We then return an appropriate message to the user.
- We then check if the url contains
code
and state
parameters or not. If these are not in the URL, this means that someone tried to access the URL directly. So we redirect them to the login page.
- After handling all the side cases, we finally handle the case where the user has successfully authenticated our app.
- In this case, we create a new
OAuth2Session
object by passing the state
parameter.
- Then we try to get an
access_token
from Google using
If error occurs, we return appropriate message to user.
- After getting the
token
successfully, we again create a new OAuth2Session
by setting the token
parameter.
- Finally we try to access the user information using
The user information is a JSON of the form:
{
"family_name": "Doe",
"name": "John Doe",
"picture": "https://lh3.googleusercontent.com/-asdasdas/asdasdad/asdasd/daadsas/photo.jpg",
"locale": "en",
"gender": "male",
"email": "john@gmail.com",
"link": "https://plus.google.com/+JohnDoe",
"given_name": "John",
"id": "1109367330250025112153346",
"verified_email": true
}
After getting the user information, its upto us to how to handle the information. In the callback code, we handle the information by:
- First we check if a user with the retrieved
email
is already in the DB or not. If user is not found, we create a new user
and assign the email
to it.
- Then we set the other attributes like
avatar
, tokens
and then finally commit the changes to DB.
- After commiting the changes, we login the user using
login_user(user)
and then redirect the user to home page.
Full Code - app.py
To run the above code, first create the DB by opening the python console and executing:
Then create a test ssl certificate using werkzeug
:
Finally create a file run.py
in the same directory as app.py
and add the following code and finally run python run.py
:
Edit
From the comments, I have come to know that many of you have been running into problems with successfully running the flask server. It was a mistake from my side which I have realized in last few days.
In Step 6 above, while adding a redirect uri in the Google Project Console, I attached a screenshot in which, the redirect uri
was http://localhost:5000/gCallback
instead of it starting with https
. So I have updated the screenshot in which I have added both http
and https
. You should add both http
and https
URLs as redirect uri. Also add both http://localhost:5000
and https://localhost:5000
in the Authorized Javascript Origins.
Also if you want to simply run the flask server on http instead of https, add the following 2 lines of code at the top of your app.py
Also Have a look some common configurations related changes:
STEP 1.
First go to Google Developers Console. Sign in using your Google credentials if you haven’t already. There will be a list of projects.
STEP 2.
Then Create new projects in credential select oauth2 and give correct url of your project .
STEP 3.
After you will be presented with a dialog box having your client ID and client secret. Copy both the strings and save in a text file as we will be needing these later.
STEP 4.
Then Create token field in database which will stores the access and refresh tokens JSON, dumped as string.
STEP 5.
If using flask-login to manage user sessions, we can check whether a user is logged in or not. If not logged in, we redirect the user to a login page that contains the link to Google login.create Auth Class in config.py and defined variables for
client_Id,
Client_secret,
Redirect_url,
Auth_url=https://accounts.google.com/o/oauth2/auth, token_url=https://accounts.google.com/o/oauth2/token,
Here,
redirect_url is what we set in Google Developers Console,
Auth_url is where the user is taken to for Google login,
token_urlI is used to exchange a temporary token for an access_token and
user_info is the URL used for retrieving user information like name, email, etc after successful authentication.
Scope is the types of user information that we will be accessing after the user authenticates our app. Google OAuth2 Playground has a list of scopes that can be added.
STEP 6.
We create a helper function get_google_auth that we will use to create OAuth2Session object based on the arguments provided(state,token).
-
When none of the parameters are provided, e.g. google = get_google_auth(), it creates a new OAuth2Session with a new state.
-
If state is provided, that means we have to get a token.
-
If token is provided, that means we only have to get an access_token and this is the final step.
STEP 7.
Then in login view create google authorization url using oauth session given by helper function google = get_google_auth(), render it in template.
auth_url, state = google.authorization_url(
Auth.AUTH_URI, access_type='offline')
STEP 8.
Then in callback function :
-
We check if a user is already logged in. If yes, we then redirect them to the home page.
-
Then we check if the url has an error query parameter. This check is done to handle cases where a user after going to the Google login page, denies access. We then return an appropriate message to the user.
-
We then check if the url contains code and state parameters or not. If these are not in the URL, this means that someone tried to access the URL directly. So we redirect them to the login page.
-
After handling all the side cases, we finally handle the case where the user has successfully authenticated our app.
-
In this case, we create a new OAuth2Session object by passing the stateparameter.
-
Then we try to get an access_token from Google using
-
token = google.fetch_token(
Auth.TOKEN_URI,
client_secret=Auth.CLIENT_SECRET,
authorization_response=request.url)
-
If error occurs, we return appropriate message to user.
-
After getting the token successfully, we again create a new OAuth2Session by setting the token parameter.
-
Finally we try to access the user information using
-
google = get_google_auth(token=token)
resp = google.get(Auth.USER_INFO)
After getting the user information, its upto us to how to handle the information.