pallets-eco / flask-admin

Simple and extensible administrative interface framework for Flask
https://flask-admin.readthedocs.io
BSD 3-Clause "New" or "Revised" License
5.78k stars 1.57k forks source link

flask-admin & before_request #1996

Open macmule opened 4 years ago

macmule commented 4 years ago

Hi folks,

Looking to add Okta auth to an app as per: https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html

But the Admin routes seem to be bypassing the before_request, any ideas? (example below)

@app.before_request
def before_request():
    if oidc.user_loggedin:
        g.user = okta_client.get_user(oidc.user_getfield("sub"))
    else:
        g.user = None

Really want the whole site behind Okta auth.

macmule commented 4 years ago

And.. I’m holding it wrong.

Moving to a simple app showed that the issue is in my @app.before_request as I need to be somewhat selective.

Nothing to do with this fine module that has served me well for years.

macmule commented 4 years ago

Sorry, me again.. below is a little example:

from datetime import datetime
from flask import Flask, g, redirect, session, url_for
from flask_admin import Admin
from flask_admin.base import MenuLink
from flask_oidc import OpenIDConnect  
from okta import UsersClient

app = Flask(__name__)

app.config['OIDC_CLIENT_SECRETS'] = 'client_secrets.json'
app.config['OIDC_COOKIE_SECURE'] = False
app.config['OIDC_CALLBACK_ROUTE'] = '/oidc/callback'
app.config['OIDC_SCOPES'] = ['openid', 'email', 'profile']
app.config['SECRET_KEY'] = SOME_KEY
oidc = OpenIDConnect(app)
okta_client = UsersClient(ORG URL, AUTH_TOKEN)

admin = Admin(app, name='flask-okta', template_mode='bootstrap3')

#@admin.before_request
@app.before_request
def before_request():
    g.user = None
    if oidc.user_loggedin:
        print(oidc)
        g.user = okta_client.get_user(oidc.user_getfield('email'))
        print(g.user)
        print(oidc.get_access_token())
    else:
        g.user = None

@app.route('/')
@app.route('/admin')
@app.route('/admin/')
@oidc.require_login
def index():
    return 'Welcome'

@app.route('/greet')
@oidc.require_login
def greet():
    time = datetime.now().hour
    if time >= 0 and time < 12:
        return 'Good Morning!'
    elif time >= 12 and time < 16:
        return 'Good Afternoon!'
    else:
        return 'Good Evening!'

@oidc.require_login
def login():
    return redirect(url_for('.greet'))

@app.route('/logout')
def logout():
    oidc.logout()
    return redirect(url_for('.index'))

The @app.before_request is what I want to add for Admin, but @admin.before_request doesn't work.. any pointers?

macmule commented 4 years ago

If this still the best method? https://github.com/flask-admin/flask-admin/pull/237#issuecomment-18726857

guillp commented 4 years ago

I had to do quite the same thing very recently, so I provided an example in #1998 . It takes a radically different approach than what your blog post suggests, but it benefits from flask-security/flask-login built-in features, which I believe is much better than what pyoidc offers.

macmule commented 4 years ago

@guillp thanks!

So I can likely drop all the db parts right? I'll try that anyways

macmule commented 4 years ago

No luck so far, had to add requests & LoginManager to flask_login so far

macmule commented 4 years ago

Must be doing something wrong, now hitting:

RecursionError: maximum recursion depth exceeded

macmule commented 4 years ago

Ok, trashed it & progress.. thanks again @guillp.. will report back

guillp commented 4 years ago

@guillp thanks!

So I can likely drop all the db parts right? I'll try that anyways

Depending on your requirements regarding authorization in your app, you might need the DB to store at least Roles anyway. If you don't need to have fine-grained permissions you can probably use Flask-Login only instead of Flask-Security, and you don't have to store anything user related in DB; you can rely only on informations provided by the OIDC provider instead.

My example should work pretty much OOTB, all required modules are listed in examples/auth-authlib-oidc/requirements.txt, you only need to set the client_id, client_secret and OIDC metadata url. If it doesn't, then let me know, I'll fix it.

macmule commented 4 years ago

In the end, got this working with flask-dance.

But thanks for the help @guillp.

macmule commented 2 years ago

Hit this once more, sadly.

Looks like @oidc.require_login doesn't seem to work with the routes such as @app.route('/admin') and @app.route('/admin/')

But does work for '/', so when folks navigate to '/' they are prompted to auth, but not an /admin/ route