Integrating Google OAuth2 login into a Python web application not only provides a seamless sign‑in experience for users, but also boosts your site’s credibility and SEO by leveraging a trusted authentication provider. In this guide we’ll walk through every step—from creating credentials in the Google Cloud Console to writing clean, production‑ready Python code—so you can launch a secure “Sign in with Google” button in minutes.
Why Choose Google OAuth2 for Python Apps?
- Universal access: Over a billion Google accounts mean users rarely need to create a new password.
- Built‑in security: Google handles password storage, multi‑factor authentication, and token revocation.
- SEO advantage: Faster sign‑in reduces bounce rates, which search engines interpret as a quality signal.
- Scalable: OAuth2 works the same for small Flask projects and large Django deployments.
Prerequisites
- Python 3.8+ installed on your development machine.
- A Google account to access the Cloud Console.
- Basic familiarity with Flask or Django (the examples use Flask for brevity).
- Virtual environment tool (venv, pipenv, or poetry) to keep dependencies isolated.
Step 1 – Create OAuth2 Credentials in Google Cloud Console
- Navigate to Google Cloud Console → APIs & Services → Credentials.
- Click Create Credentials → OAuth client ID.
- Select Web application as the application type.
- Enter a name (e.g.,
MyPythonApp). - Under Authorized JavaScript origins, add your development URL (e.g.,
http://localhost:5000). - Under Authorized redirect URIs, add the callback endpoint that will receive the authorization code, such as
http://localhost:5000/auth/callback. - Save. Google will display a Client ID and Client Secret**—store them securely (e.g., in a
.envfile).
Step 2 – Install Required Python Packages
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install Flask requests-oauthlib python-dotenv
Flask provides the web framework, requests‑oauthlib handles the OAuth2 flow, and python‑dotenv loads environment variables securely.
Step 3 – Configure Environment Variables
Create a .env file in the project root:
GOOGLE_CLIENT_ID=YOUR_CLIENT_ID.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=YOUR_CLIENT_SECRET
SECRET_KEY=your_flask_secret_key
Step 4 – Implement the OAuth2 Flow with Flask
4.1 Initialize Flask and OAuth Session
from flask import Flask, redirect, url_for, session, request, jsonify
from requests_oauthlib import OAuth2Session
from urllib.parse import urlparse, urljoin
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')
# Google OAuth2 endpoints
AUTHORIZATION_BASE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
TOKEN_URL = 'https://oauth2.googleapis.com/token'
USER_INFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
# Scopes we need – email and profile are typical
SCOPE = ['openid', 'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile']
4.2 Helper to Verify Safe Redirects
def is_safe_url(target):
host_url = request.host_url
ref_url = urlparse(host_url)
test_url = urlparse(urljoin(host_url, target))
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
4.3 Login Route – Redirect to Google
@app.route('/login')
def login():
google = OAuth2Session(
client_id=os.getenv('GOOGLE_CLIENT_ID'),
scope=SCOPE,
redirect_uri=url_for('callback', _external=True)
)
authorization_url, state = google.authorization_url(
AUTHORIZATION_BASE_URL,
access_type='offline', # Request refresh token
prompt='consent' # Force consent screen for fresh refresh token
)
# Store state in session to protect against CSRF
session['oauth_state'] = state
return redirect(authorization_url)
4.4 Callback Route – Exchange Code for Tokens
@app.route('/auth/callback')
def callback():
# Verify state to mitigate CSRF attacks
if request.args.get('state') != session.get('oauth_state'):
return 'State mismatch', 400
google = OAuth2Session(
client_id=os.getenv('GOOGLE_CLIENT_ID'),
redirect_uri=url_for('callback', _external=True),
state=session['oauth_state']
)
token = google.fetch_token(
TOKEN_URL,
client_secret=os.getenv('GOOGLE_CLIENT_SECRET'),
authorization_response=request.url
)
# Save token (including refresh_token) securely – here we use session for demo
session['google_token'] = token
# Retrieve user profile
resp = google.get(USER_INFO_URL)
user_info = resp.json()
session['user'] = {
'id': user_info['sub'],
'email': user_info['email'],
'name': user_info.get('name')
}
# Redirect to a protected page
next_url = request.args.get('next') or url_for('profile')
return redirect(next_url) if is_safe_url(next_url) else redirect(url_for('profile'))
4.5 Protected Profile Page
@app.route('/profile')
def profile():
if 'user' not in session:
return redirect(url_for('login', next=request.path))
user = session['user']
return f"""
Welcome, {user['name']}
Email: {user['email']}
Google ID: {user['id']}
Logout
"""
4.6 Logout Route
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
Step 5 – Managing Refresh Tokens
Google issues a refresh_token only the first time a user consents, unless you force the consent screen (as shown with prompt='consent'). Store the refresh token in a persistent store (database, encrypted vault) rather than the user session. When the access token expires, use the refresh token to obtain a new one:
def refresh_google_token(refresh_token):
extra = {
'client_id': os.getenv('GOOGLE_CLIENT_ID'),
'client_secret': os.getenv('GOOGLE_CLIENT_SECRET'),
}
oauth = OAuth2Session(client_id=os.getenv('GOOGLE_CLIENT_ID'))
new_token = oauth.refresh_token(
TOKEN_URL,
refresh_token=refresh_token,
**extra
)
return new_token
Step 6 – Common Pitfalls & How to Avoid Them
- State mismatch errors: Always verify the
stateparameter returned by Google against the value stored in the session. - Redirect URI mismatch: The URI you register in the Cloud Console must exactly match the
redirect_uriused in code (including trailing slashes). - Missing scopes: If you need additional Google APIs (e.g., Drive), add the corresponding scopes to the
SCOPElist. - Refresh token loss: Request
access_type='offline'andprompt='consent'during the first login, then persist the refresh token. - Insecure storage of secrets: Never commit
.envfiles to version control; use environment variables on production servers.
Step 7 – Security Best Practices
- Use HTTPS in production. OAuth2 tokens are bearer tokens; transmitting them over HTTP exposes them to interception.
- Validate ID tokens. Google returns an
id_token(JWT). Verify its signature and claims (issuer, audience, expiration) using thegoogle-authlibrary for added security. - Implement token revocation. Provide a logout flow that calls
https://oauth2.googleapis.com/revoke?token={token}to invalidate the token on Google’s side. - Set short-lived access tokens. Rely on refresh tokens for long‑term sessions, reducing the window for token abuse.
- Apply SameSite cookie attribute. In Flask, set
SESSION_COOKIE_SAMESITE = 'Lax'or'Strict'to mitigate CSRF.
Step 8 – Testing the Integration
Before deploying, run the Flask app locally and verify each step:
export FLASK_APP=app.py
flask run
Visit http://localhost:
Leave a Reply