pallets / quart

An async Python micro framework for building web applications.
https://quart.palletsprojects.com
MIT License
2.9k stars 158 forks source link

OIDC providers for quart #103

Closed fim closed 4 years ago

fim commented 4 years ago

I have spent a few hours trying to get OIDC working with flask_oidc and quart but I have failed so far. This was me trying to integrate flask_oidc with an existing application so it took quite a bit of time to figure out what the problem is (and I'm not 100% sure I have found out yet).

However, after a lot of debugging I decided to build a small example to try and repro this and I got started with https://github.com/okta/samples-python-flask/tree/master/custom-login. I tested to make sure the flask implementation works and then I replaced all the flask bits with quart. The result looks like this:

import base64

import quart.flask_patch  # noqa
from quart import Quart
from quart import render_template, url_for, redirect, session, json
from flask_oidc import OpenIDConnect

app = Quart(__name__)
app.config.update({
    'SECRET_KEY': 'SomethingNotEntirelySecret',
    'OIDC_CLIENT_SECRETS': './client_secrets.json',
    'OIDC_DEBUG': True,
    'OIDC_ID_TOKEN_COOKIE_SECURE': False,
    'OIDC_SCOPES': ["openid", "profile"],
    'OVERWRITE_REDIRECT_URI': 'https://localhost:5000/oidc/callback',
    'OIDC_CALLBACK_ROUTE': '/oidc/callback'
})

oidc = OpenIDConnect(app)

@app.route("/")
async def home():
    return await render_template("home.html", oidc=oidc)

@app.route("/login")
async def login():
    bu = oidc.client_secrets['issuer'].split('/oauth2')[0]
    cid = oidc.client_secrets['client_id']

    destination = 'https://localhost:5000/'
    state = {
        'csrf_token': session['oidc_csrf_token'],
        'destination': oidc.extra_data_serializer.dumps(destination).decode('utf-8')
    }

    return await render_template("login.html", oidc=oidc, baseUri=bu, clientId=cid, state=base64_to_str(state))

@app.route("/profile")
async def profile():
    info = oidc.user_getinfo(["sub", "name", "email", "locale"])

    return await render_template("profile.html", profile=info, oidc=oidc)

@app.route("/logout", methods=["POST"])
async def logout():
    oidc.logout()

    return redirect(url_for("home"))

def base64_to_str(data):
    return str(base64.b64encode(json.dumps(data).encode('utf-8')), 'utf-8')

if __name__ == '__main__':
    app.run(port=5000, certfile="server.crt", keyfile="server.key", debug=True)

Everything else was left the same but the quart example doesn't work. It seems like the g.oidc_id_token is not getting populated and I am suspecting this might be due to how quart handles/resets g but I could be wrong.

Before spending more time looking into this, has anyone gotten flask_oidc to work with quart and if not are there any alternatives I can try? If not, maybe there's some workaround for the example described above which I have yet to figure out?

fim commented 4 years ago

I couldn't get flask_oidc to work so I gave up and used authlib (authlib.integrations.flask_client.OAuth to be precise) which worked fine. It'd still be interesting to see whether there's a way to get flask_oidc working.

pgjones commented 4 years ago

I've not tried flask_oidc, there is a sans-io library oauthlib that might help.

What do you think is going on with g? Are there any errors?

fim commented 4 years ago

No, no errors came up. Auth seemed to work but no user information persisted across requests. I gave up trying to investigate what exactly is going on when I realised it must be something internally to quart so I ended up implementing the same using https://github.com/lepture/authlib which worked out of the box and with minimal effort.

sanderfoobar commented 4 years ago

Here is a simple implementation with flask, requests, and flask-session: https://gist.github.com/xmrdsc/0076a5247ff345c1fd698913d99f3148

Easy to rewrite to aiohttp + quart.

Edit: I made a extension https://github.com/sanderfoobar/quart-session-openid which will add OIDC support to your Quart application.