Flask – MongoDB – Protect routes

Issue

I am new to Flask and I built a basic web app using Flask. This app will be used by a single user. The user must be connected in order to access any routes. What would be the easiest and most secure way to create new routes on my app and make sure that the user is logged in before they are able to access the page?

I added this route and I am able to access the page even if I am logged in.

@login_required
@app.route('/secret')
def secret():
    return "hello world"

app.py

from flask import Flask, render_template, url_for, request, session, redirect
from flask_pymongo import PyMongo
import bcrypt

app = Flask(__name__)

app.config['MONGO_DBNAME'] = xxx'
app.config['MONGO_URI'] = 'xxxx'

mongo = PyMongo(app)

@app.route('/')
def index():
    if 'username' in session:
        return 'You are logged in as ' + session['username']

    return render_template('index.html')

@app.route('/login', methods=['POST'])
def login():
    users = mongo.db.users
    login_user = users.find_one({'name' : request.form['username']})

    if login_user:
        if bcrypt.hashpw(request.form['pass'].encode('utf-8'), login_user['password']) == login_user['password']:
            session['username'] = request.form['username']
            return redirect(url_for('index'))

    return 'Invalid username/password combination'

@app.route('/register', methods=['POST', 'GET'])
def register():
    if request.method == 'POST':
        users = mongo.db.users
        existing_user = users.find_one({'name' : request.form['username']})

        if existing_user is None:
            hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
            users.insert({'name' : request.form['username'], 'password' : hashpass})
            session['username'] = request.form['username']
            return redirect(url_for('index'))
        
        return 'That username already exists!'

    return render_template('register.html')

@app.route('/logout')
def logout():
    session.pop('username', None)
    return render_template('index.html')

if __name__ == '__main__':
    app.secret_key = 'mysecret'
    app.run(debug=True, port='3500')

Solution

You can use the flask-login module for this. It handles user authentication, allowing routes to be protected with the @login_required decorator.

I recommend reading the documentation, as you need to provide a class with certain properties to represent your users and you need to implement a method that returns a user based on a given identifier.

When a user is logged in, the templates can access the current_user variable and it’s properties (as defined by you; name, email etc).

Here is a simple example of the python code (I have not included the static files or templates).

from flask import Flask, render_template, request, url_for, request, redirect, abort
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_pymongo import PyMongo
from flask_bcrypt import Bcrypt
from urllib.parse import urlparse, urljoin
import sys

# Import User classes
from user import User, Anonymous

# Create app
app = Flask(__name__)

# Configuration
app.config['MONGO_DBNAME'] = 'database_name'
app.config['MONGO_URI'] = 'mongo_database_uri'
app.secret_key = 'change this before production'

# Create Pymongo
mongo = PyMongo(app)

# Create Bcrypt
bc = Bcrypt(app)

# Create login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = Anonymous
login_manager.login_view = "login"


# ROUTES

@app.route('/')
def index():
    return render_template('index.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        if current_user.is_authenticated:
            return redirect(url_for('/index'))
        return render_template('login.html')
    users = mongo.db.users
    user_data = users.find_one({'email': request.form['email']}, {'_id' : 0 })
    if user_data:
        if bc.check_password_hash(user_data['password'], request.form['pass']):
            user = User(user_data['title'], user_data['first_name'], user_data['last_name'], user_data['email'], user_data['password'], user_data['id'])
            login_user(user)

            #Check for next argument (direct user to protected page they wanted)
            next = request.args.get('next')
            if not is_safe_url(next):
                return abort(400)

            return redirect(next or url_for('profile'))

    return 'Invalid email or password'


@app.route('/register', methods=['POST', 'GET'])
def register():
    if request.method == 'POST':
        users = mongo.db.users
        existing_user = users.find_one({'email' : request.form['email']}, {'_id' : 0 })

        if existing_user is None:
            logout_user()
            hashpass = bc.generate_password_hash(request.form['pass']).decode('utf-8')
            new_user = User(request.form['title'], request.form['first_name'], request.form['last_name'], request.form['email'], hashpass)
            login_user(new_user)
            users.insert_one(new_user.dict())
            return redirect(url_for('profile'))

        return 'That email already exists!'

    return render_template('register.html')


@app.route('/profile', methods=['GET'])
@login_required
def profile():
    return render_template('profile.html')


@app.route('/list', methods=['GET'])
@login_required
def list():
    #if current_user.id:
    log(current_user.is_authenticated)
    all_users = mongo.db.users.find({}, {'_id' : 0 })
    return render_template('list.html', users = all_users)


@app.route('/logout', methods=['GET'])
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))


# Login Manager requirements

@login_manager.user_loader
def load_user(userid):
    # Return user object or none
    users = mongo.db.users
    user = users.find_one({'id': userid}, {'_id' : 0 })
    if user:
        log(user)
        return User(user['title'], user['first_name'], user['last_name'], user['email'], user['password'], user['id'])
    return None


# Safe URL
def is_safe_url(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    return test_url.scheme in ('http', 'https') and \
           ref_url.netloc == test_url.netloc


# Run app
if __name__ == '__main__':
    app.run(debug=True)

I have a functioning basic template app using flask, flask-login and flask-pymongo at https://github.com/chriswilson1982/flask-mongo-app

Answered By – Chris

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published