Flask OAuth Implementation
Introduction
OAuth (Open Authorization) is an open standard protocol that allows users to grant websites or applications access to their information on other services without giving them their passwords. In this tutorial, we'll learn how to implement OAuth in a Flask application, enabling users to log in using their existing accounts from providers like Google, GitHub, or Facebook.
OAuth offers several advantages over traditional authentication:
- Security: Users don't need to share their credentials with your application
- Convenience: Users don't need to create and remember another account
- Additional Information: OAuth often provides access to user profile data
- Trust: Leverages trusted identity providers for authentication
Prerequisites
Before we begin, make sure you have:
- Basic knowledge of Flask
- Basic understanding of HTTP and web authentication
- Python 3.6+ installed
- Familiarity with virtual environments
Setting Up Your Environment
Let's start by setting up a new Flask project with the necessary dependencies.
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install required packages
pip install flask flask-dance flask-login flask-sqlalchemy
Understanding OAuth Flow
Before diving into the code, let's understand how the OAuth flow works:
- User Initiates Login: User clicks on "Login with Google/GitHub/etc."
- Redirection to Provider: Your app redirects to the OAuth provider
- User Authentication: User authenticates with the provider
- Authorization: User authorizes your app to access certain information
- Callback: Provider redirects back to your app with an authorization code
- Token Exchange: Your app exchanges the code for access tokens
- User Information: Your app uses the token to fetch user information
- Session Creation: Your app creates a local user session
Implementing OAuth with Flask-Dance
Flask-Dance is a popular extension that simplifies OAuth implementation in Flask applications. Let's create a simple application with GitHub OAuth integration.
Step 1: Register Your Application with the OAuth Provider
First, you'll need to register your application with GitHub:
- Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
- Fill in application name, homepage URL (e.g.,
http://localhost:5000
) - Set the authorization callback URL to:
http://localhost:5000/login/github/authorized
- After registration, you'll receive a client ID and client secret
Step 2: Create the Flask Application
Create a file named app.py
with the following code:
import os
from flask import Flask, redirect, url_for, flash, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, login_required
from flask_dance.consumer import oauth_authorized
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin, SQLAlchemyStorage
from flask_dance.contrib.github import make_github_blueprint, github
from sqlalchemy.orm.exc import NoResultFound
# Configuration
app = Flask(__name__)
app.config["SECRET_KEY"] = "your-secret-key" # Replace with a real secret key
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///users.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# Set OAuth environment variables (alternatively use .env file with python-dotenv)
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # For development only
os.environ['GITHUB_OAUTH_CLIENT_ID'] = 'your-github-client-id' # Replace with your client ID
os.environ['GITHUB_OAUTH_CLIENT_SECRET'] = 'your-github-client-secret' # Replace with your client secret
# Initialize SQLAlchemy
db = SQLAlchemy(app)
# User model
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(250), unique=True)
github_id = db.Column(db.String(250), unique=True, nullable=True)
# OAuth model
class OAuth(OAuthConsumerMixin, db.Model):
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User)
# Initialize login manager
login_manager = LoginManager(app)
login_manager.login_view = "github.login"
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# Create GitHub blueprint
github_blueprint = make_github_blueprint(
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user)
)
app.register_blueprint(github_blueprint, url_prefix="/login")
# Routes
@app.route("/")
def index():
return render_template("index.html")
@app.route("/profile")
@login_required
def profile():
return render_template("profile.html")
@app.route("/logout")
@login_required
def logout():
logout_user()
flash("You have been logged out")
return redirect(url_for("index"))
# OAuth handler
@oauth_authorized.connect_via(github_blueprint)
def github_logged_in(blueprint, token):
if not token:
flash("Failed to log in with GitHub.", category="error")
return False
resp = github.get("/user")
if not resp.ok:
flash("Failed to fetch user info from GitHub.", category="error")
return False
github_info = resp.json()
github_user_id = str(github_info["id"])
# Find this OAuth token in the database or create it
query = OAuth.query.filter_by(
provider=blueprint.name,
provider_user_id=github_user_id,
)
try:
oauth = query.one()
except NoResultFound:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=github_user_id,
token=token,
)
# Now, figure out what to do with this token
if oauth.user:
# If this OAuth token already has an associated local user,
# log in that user
login_user(oauth.user)
flash("Successfully signed in with GitHub.")
else:
# Create a new local user account for this user
user = User(
username=github_info["login"],
github_id=github_user_id,
)
# Associate the new local user with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user)
flash("Successfully signed in with GitHub.")
# Prevent Flask-Dance from saving the OAuth token again
return False
# Initialize database
with app.app_context():
db.create_all()
if __name__ == "__main__":
app.run(debug=True)
Step 3: Create HTML Templates
Create a templates directory and add the following files:
templates/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask OAuth Demo</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.messages {
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
background-color: #f0f0f0;
}
.btn {
display: inline-block;
padding: 10px 15px;
background-color: #333;
color: white;
text-decoration: none;
border-radius: 5px;
margin: 10px 0;
}
</style>
</head>
<body>
<header>
<h1>Flask OAuth Demo</h1>
<nav>
<a href="{{ url_for('index') }}">Home</a> |
{% if current_user.is_authenticated %}
<a href="{{ url_for('profile') }}">Profile</a> |
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('github.login') }}">Login with GitHub</a>
{% endif %}
</nav>
</header>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="messages">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
templates/index.html:
{% extends "base.html" %}
{% block content %}
<h2>Welcome to Flask OAuth Demo</h2>
{% if current_user.is_authenticated %}
<p>You are logged in as {{ current_user.username }}</p>
<a href="{{ url_for('profile') }}" class="btn">View Profile</a>
{% else %}
<p>This demo shows how to implement OAuth authentication in Flask.</p>
<a href="{{ url_for('github.login') }}" class="btn">Login with GitHub</a>
{% endif %}
{% endblock %}
templates/profile.html:
{% extends "base.html" %}
{% block content %}
<h2>User Profile</h2>
<div>
<p><strong>Username:</strong> {{ current_user.username }}</p>
<p><strong>GitHub ID:</strong> {{ current_user.github_id }}</p>
<p><strong>User ID:</strong> {{ current_user.id }}</p>
</div>
{% if github.authorized %}
<h3>GitHub API Data</h3>
<div id="github-data">
<p>Loading GitHub data...</p>
</div>
<script>
// This script fetches data from the GitHub API via our Flask backend
fetch('/github-data')
.then(response => response.json())
.then(data => {
const container = document.getElementById('github-data');
container.innerHTML = `
`;
})
.catch(error => {
document.getElementById('github-data').innerHTML =
'<p>Error loading GitHub data</p>';
console.error('Error:', error);
});
</script>
{% endif %}
<br>
<a href="{{ url_for('logout') }}" class="btn">Logout</a>
{% endblock %}
Step 4: Add a GitHub Data Endpoint
Add this route to your app.py
file to fetch GitHub data for the user profile page:
@app.route("/github-data")
@login_required
def github_data():
if not github.authorized:
return {"error": "Not authorized with GitHub"}, 403
resp = github.get("/user")
if not resp.ok:
return {"error": f"Failed to fetch user data: {resp.text}"}, 500
return resp.json()
Step 5: Run Your Application
Run your application with:
python app.py
Visit http://localhost:5000 in your browser and click "Login with GitHub" to test your OAuth implementation.
Example Output
Once implemented, users will be able to:
- Click "Login with GitHub" on your home page
- Be redirected to GitHub's authentication page
- Grant permissions to your application
- Be redirected back to your application as an authenticated user
- View their profile information pulled from GitHub
Implementing Multiple OAuth Providers
You can add more OAuth providers to your application. Here's how you would add Google authentication:
# Add the import
from flask_dance.contrib.google import make_google_blueprint, google
# Add this after initializing the github_blueprint
google_blueprint = make_google_blueprint(
client_id="your-google-client-id",
client_secret="your-google-client-secret",
scope=["profile", "email"],
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user)
)
app.register_blueprint(google_blueprint, url_prefix="/login")
# Add this route for Google login in your HTML templates
# <a href="{{ url_for('google.login') }}">Login with Google</a>
# Add the handler for Google OAuth
@oauth_authorized.connect_via(google_blueprint)
def google_logged_in(blueprint, token):
if not token:
flash("Failed to log in with Google.", category="error")
return False
resp = google.get("/oauth2/v1/userinfo")
if not resp.ok:
flash("Failed to fetch user info from Google.", category="error")
return False
google_info = resp.json()
google_user_id = google_info["id"]
# Similar logic to the GitHub handler follows...
# Find or create OAuth record
# Find or create user
# Log in the user
return False
Security Considerations
When implementing OAuth, keep these security considerations in mind:
- Always use HTTPS in production (the
OAUTHLIB_INSECURE_TRANSPORT
environment variable is only for development) - Keep client secrets secure (use environment variables or a secure configuration method)
- Validate all tokens and user data
- Implement CSRF protection (Flask-Dance handles this by default)
- Consider scope limitations (only request the permissions you actually need)
- Implement proper token storage and refresh (Flask-Dance with SQLAlchemyStorage helps with this)
Extending Your OAuth Implementation
Here are some ways to extend your OAuth implementation:
- Custom user data: Store additional user profile information
- Role-based permissions: Assign roles based on OAuth provider data
- API access: Use OAuth tokens to access provider APIs
- Token refresh: Implement automatic token refreshing for long-lived sessions
- Account linking: Allow users to connect multiple OAuth providers to one account
Summary
In this tutorial, we've learned how to implement OAuth authentication in a Flask application using Flask-Dance. We've covered:
- Setting up a Flask project with the necessary dependencies
- Understanding the OAuth authentication flow
- Registering an application with an OAuth provider
- Implementing GitHub OAuth with Flask-Dance
- Creating the necessary database models and templates
- Handling the OAuth callback and user creation
- Fetching and displaying user data from the provider
- Adding multiple OAuth providers
OAuth provides a secure and convenient authentication method that leverages existing user accounts from trusted providers, reducing friction in your application's onboarding process.
Additional Resources
- Flask-Dance Documentation
- OAuth 2.0 Simplified
- GitHub OAuth Documentation
- Google OAuth Documentation
Exercises
- Implement Google OAuth in addition to GitHub OAuth
- Add a feature to display the user's GitHub repositories on their profile page
- Implement a "Link Account" feature that allows users to connect multiple OAuth providers to one account
- Create a user settings page that shows which OAuth providers are connected
- Implement token refreshing logic to handle expired OAuth tokens
By completing this tutorial, you should have a solid understanding of how to implement OAuth authentication in your Flask applications, providing users with a secure and convenient way to log in using their existing accounts.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)