<- back
view in plain-text
2020-06-24

Flask-JWT-Extended × Flask-Login

Apparently I do webshit now

For the past few months, I’ve been working on building a backend for $STARTUP, with a bunch of friends. I’ll probably write in detail about it when we launch our beta. The backend is your bog standard REST API, built on Flask—if you didn’t guess from the title already.

Our existing codebase heavily relies on Flask-Login; it offers some pretty neat interfaces for dealing with users and their states. However, its default mode of operation—sessions—don’t really fit into a Flask app that’s really just an API. It’s not optimal. Besides, this is what JWTs were built for.

I won’t bother delving deep into JSON web tokens, but the general flow is like so:

  • client logs in via say /login
  • a unique token is sent in the response
  • each subsequent request authenticated request is sent with the token

The neat thing about tokens is you can store stuff in them—“claims”, as they’re called.

returning an access_token to the client

The access_token is sent to the client upon login. The idea is simple, perform your usual checks (username / password etc.) and login the user via flask_login.login_user. Generate an access token using flask_jwt_extended.create_access_token, store your user identity in it (and other claims) and return it to the user in your 200 response.

Here’s the excerpt from our codebase.

access_token = create_access_token(identity=email)
login_user(user, remember=request.json["remember"])
return good("Logged in successfully!", access_token=access_token)

But, for login_user to work, we need to setup a custom user loader to pull out the identity from the request and return the user object.

defining a custom user loader in Flask-Login

By default, Flask-Login handles user loading via the user_loader decorator, which should return a user object. However, since we want to pull a user object from the incoming request (the token contains it), we’ll have to write a custom user loader via the request_loader decorator.

# Checks the 'Authorization' header by default.
app.config["JWT_TOKEN_LOCATION"] = ["json"]

# Defaults to 'identity', but the spec prefers 'sub'.
app.config["JWT_IDENTITY_CLAIM"] = "sub"

@login.request_loader
def load_person_from_request(request):
    try:
        token = request.json["access_token"]
    except Exception:
        return None
    data = decode_token(token)
    # this can be your 'User' class
    person = PersonSignup.query.filter_by(email=data["sub"]).first()
    if person:
        return person
    return None

There’s just one mildly annoying thing to deal with, though. Flask-Login insists on setting a session cookie. We will have to disable this behaviour ourselves. And the best part? There’s no documentation for this—well there is, but it’s incomplete and points to deprecated functions.

To do this, we define a custom session interface, like so:

from flask.sessions import SecureCookieSessionInterface
from flask import g
from flask_login import user_loaded_from_request

@user_loaded_from_request.connect
def user_loaded_from_request(app, user=None):
    g.login_via_request = True


class CustomSessionInterface(SecureCookieSessionInterface):
    def should_set_cookie(self, *args, **kwargs):
        return False

    def save_session(self, *args, **kwargs):
        if g.get("login_via_request"):
            return
        return super(CustomSessionInterface, self).save_session(*args, **kwargs)


app.session_interface = CustomSessionInterface()

In essence, this checks the global store g for login_via_request and and doesn’t set a cookie in that case. I’ve submitted a PR upstream for this to be included in the docs (#514).

Questions or comments? Send an email to ~icyphox/x@lists.sr.ht—my public inbox.


Gripes With Go

You’ve read a lot of posts about the shortcomings of the Go programming language, so what’s one more. Lack of sum types Type assertions Date and Time Statements over Expressions Erroring out on unused variables Error handling Lack of Sum types A “Sum” ty…

via nerdypepper's μblog on Aug 01, 2020

03/08/2020: This month in KISS (#4)

Welcome to month 4!…

via KISS Linux Blog on Aug 03, 2020

pkg.go.dev is more concerned with Google's interests than good engineering

pkg.go.dev sucks. It’s certainly prettier than godoc.org, but under the covers, it’s a failure of engineering characteristic of the Google approach. Go is a pretty good programming language. I have long held that this is not attributable to Google’s stewa…

via Drew DeVault's Blog on Aug 01, 2020

Generated by openring.py

email
x@icyphox.sh

github
icyphox

mastodon
@x@icyphox.sh

pgp
0x8A93F96F78C5D4C4

friends

Some of my friends and internet bros.

about

More about me and my work.