pythonflaskflask-loginflask-security

Simplest way to securely distinguish between admins and other users in Flask


Forgive my naivety here, but I've been looking into various solutions for role authentication in Flask, and it seems that several extensions are no longer being maintained. This got me wondering whether I require any of them beyond Flask Login for my purposes.

Is it sufficient to use if current_user.is_admin in my routes and templates to distinguish between admins and other users? If so, are there any security concerns or other drawbacks to consider with this approach vs using something like Flask Security Too? I'm realizing this approach would get very repetitive - could I create a decorator to perform this check? I don't have a solid handle on decorators yet.

routes.py

@app.route('/students', methods=['GET', 'POST'])
@login_required
def students():
    form = StudentForm()
    if current_user.is_admin:
        if form.validate_on_submit():
            student = Student(student_name=form.student_name.data, last_name=form.last_name.data, \
            student_email=form.student_email.data, parent_name=form.parent_name.data, \
            parent_email=form.parent_email.data, secondary_email=form.secondary_email.data, \
            timezone=form.timezone.data, location=form.location.data, status=form.status.data, \
            tutor=form.tutor_id.data)
            try:
                db.session.add(student)
                db.session.commit()
            except:
                db.session.rollback()
                flash(student.student_name + ' could not be added', 'error')
                return redirect(url_for('students'))
            flash(student.student_name + ' added')
            return redirect(url_for('students'))
        return render_template('students.html', title="Students", form=form)
    else:
        flash('You must have administrator privileges to access this page.', 'error')
        logout_user()
        return redirect(url_for('login'))

models.py

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...
    is_admin = db.Column(db.Boolean)

Solution

  • Created the following admin_required decorator:

    def admin_required(f):
        @login_required
        @wraps(f)
        def wrap(*args, **kwargs):
            if current_user.is_admin:
                return f(*args, **kwargs)
            else:
                flash('You must have administrator privileges to access this page.', 'error')
                logout_user()
                return redirect(login_url('login', next_url=request.url))
        return wrap
    

    Note it is important to validate next_url in your login view to avoid phishy redirects:

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        form = LoginForm()
        if form.validate_on_submit():
            ...
            next = request.args.get('next')
            if not next or url_parse(next).netloc != '':
                next = url_for('default_redirect')
            return redirect(next)
        return render_template('login.html', title="Login", form=form)