I'm working on a site where the users login via OAuth, not a password-based system.
Because of this, Flask-Security's default login page doesn't actually work for my use case, because I need the /login
endpoint for the OAuth setup. I was able to make it so that my /login
route wasn't being overridden by Flask-Security's by changing the SECURITY_LOGIN_URL
setting option.
This is all working fine, the OAuth login page shows up and returns all the information needed.
The problem kicks in because I'm also trying to utilize the @login_required
decorator.
If the user isn't logged in, instead of redirecting to my /login
page, the @login_required
decorator is redirecting to Flask-Security's page.
Obviously, the config endpoint doesn't help in this situation.
Is it possible to force Flask-Security to use my login route (OAuth) instead of its page?
Here's some example code that shows what I'm talking about with Flask-Security overriding defined routes:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, request
from flask_security import (Security, SQLAlchemyUserDatastore,
UserMixin, RoleMixin, login_required,
login_user, logout_user, current_user)
from passlib.context import CryptContext
import os
class Config:
basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG = True
PORT = 8000
HOST = "0.0.0.0"
SECRET_KEY = "foobar123"
config = Config()
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(config)
db = SQLAlchemy(app)
lm = LoginManager()
lm.init_app(app)
lm.login_view = "login"
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 User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
provider_id = db.Column(db.String(64), index=True, unique=True)
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return True
def get_id(self):
return str(self.id)
def hash_password(self, password):
self.hashed_password = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.hashed_password)
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))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
pwd_context = CryptContext(
schemes=["bcrypt", "pbkdf2_sha256", "des_crypt"],
default="bcrypt",
all__vary_rounds=0.1
)
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.route("/")
@app.route("/index")
@login_required
def index():
"""Handles calls to / and /index, return the panel"""
return render_template("index.html")
@app.route("/login")
def login():
"""Would include a bunch of OAuth stuff, not required for this example
If you try going to this endpoint, you'll get the Flask-Security /login
instead."""
return render_template("login.html")
app.run(debug=True)