Reusable Python Code Modules, Part 3 - Authentication and Authorization
Securing Your API: Implementing Reusable Authentication and Authorization Modules
Authentication and authorization are essential for securing APIs, ensuring only authorized users can access specific resources. This guide covers how to structure reusable authentication and authorization modules in Python using Flask. We will explore common libraries, compare their features, and provide practical examples and code snippets.
Common Libraries and Tools
1. Flask-JWT-Extended
Flask-JWT-Extended is a Flask extension for adding JSON Web Token (JWT) authentication to your Flask applications
Key Features
JWT creation and validation
Refresh tokens
Role-based access control
Token revocation
2. Flask-Login
Flask-Login is a user session management extension for Flask that handles user login and logout
https://flask-login.readthedocs.io/en/latest
Key Features
Session management
User authentication
Remember me functionality
Integration with Flask
3. Authlib
Authlib is a comprehensive library for integrating various authentication protocols, including OAuth 1, OAuth 2, and OpenID Connect
Key Features
OAuth 1 and OAuth 2 support
JWT and JWS support
Integration with Flask and other frameworks
Client and server implementation
4. Flask-Security
Flask-Security is a Flask extension that provides security features, including authentication, authorization, and password hashing
https://flask-security-too.readthedocs.io/en/stable
Key Features
User registration and login
Role-based access control
Password hashing and verification
Integration with Flask
Comparison
Flask-JWT-Extended: Best for JWT-based authentication with comprehensive features like refresh tokens and role-based access control
Flask-Login: Ideal for session-based authentication with simple session management
Authlib: Suitable for integrating OAuth 1, OAuth 2, and OpenID Connect with Flask
Flask-Security: Best for applications needing user registration, password management, and role-based access control
Examples
Example 1: Flask-JWT-Extended
Setup:
$ pip install flask-jwt-extended
Configuration:
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your_jwt_secret_key'
jwt = JWTManager(app)
users = {'user1': 'password1'}
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
if username in users and users[username] == password:
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
return jsonify({"msg": "Bad username or password"}), 401
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
if __name__ == '__main__':
app.run(debug=True)
Example 2: Flask-Login
Setup:
$ pip install flask-login
Configuration:
from flask import Flask, render_template, redirect, url_for, request
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin):
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password
users = {'user1': User(id=1, username='user1', password='password1')}
@login_manager.user_loader
def load_user(user_id):
return users.get('user' + str(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = users.get(username)
if user and user.password == password:
login_user(user)
return redirect(url_for('protected'))
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/protected')
@login_required
def protected():
return 'Logged in as: ' + current_user.username
if __name__ == '__main__':
app.run(debug=True)
Example 3: Authlib
Setup:
$ pip install authlib
Configuration:
from flask import Flask, redirect, url_for
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
oauth = OAuth(app)
oauth.register(
name='github',
client_id='your_client_id',
client_secret='your_client_secret',
authorize_url='https://github.com/login/oauth/authorize',
authorize_params=None,
access_token_url='https://github.com/login/oauth/access_token',
access_token_params=None,
client_kwargs={'scope': 'user:email'},
)
@app.route('/')
def homepage():
return 'Welcome to the OAuth demo'
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return oauth.github.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = oauth.github.authorize_access_token()
user = oauth.github.get('user').json()
return f'Hello, {user["login"]}!'
if __name__ == '__main__':
app.run(debug=True)
Example 4: Flask-Security
Setup:
$ pip install flask-security-too
Configuration:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///security.db'
app.config['SECURITY_PASSWORD_SALT'] = 'my_precious_two'
db = SQLAlchemy(app)
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('users', lazy='dynamic')
)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
@app.before_first_request
def create_user():
db.create_all()
if not user_datastore.get_user('user@example.com'):
user_datastore.create_user(
email='user@example.com',
password='password'
)
db.session.commit()
@app.route('/protected')
@login_required
def protected():
return 'Logged in successfully'
if __name__ == '__main__':
app.run()