3

I have a custom class Passport that contains the active user identity and permissions. I used to store it in session just like this:

p = Passport()
p.do_something_fancy()
session["passport"] = p

and it just worked. Now, after server upgrade, I am receiving this error:

TypeError: <userman.Passport instance at 0x7f06e9356f38> is not JSON serializable

I guess it is connected to some upgrades in Flask that now require the object to be JSON-serializable to be stored in session. But how do I properly make an object JSON-serializable? Perhaps I have to provide a method for serialization and also a method for deserialization, to restore the object state when it's being loaded again?

Passiday
  • 7,573
  • 8
  • 42
  • 61
  • 1
    Yes, the session serialiser switched to a JSON format to minimise the damage an attacker could do if your server-side secret was ever compromised. The pickle format previously used would let an attacker execute arbitrary code on your server if it ever got hold of the secret. – Martijn Pieters Jun 04 '14 at 11:22
  • The solution is to teach the JSON serialiser about your custom class and how to serialise it; that issue is a duplicate however. – Martijn Pieters Jun 04 '14 at 11:22
  • Thanks, hooking up a custom JSONEncoder class fixed it. How should I proceed with this post? Delete it? Also, I'm curious, how does Flask know how to restore the object state from a JSON string? Does it just copy the dictionary values to the object fields by default? – Passiday Jun 04 '14 at 11:52
  • No need to delete; it is a sign-post now for people searching for similar keywords (not that you *can* delete it now that there is an upvoted answer). But you have a valid point, the decoder will produce a dictionary here unless you post process decoded data. A `JSONDecoder` with `object_hook` method could do that, if you add a type hint in the dictionary produced by the custom encoder. – Martijn Pieters Jun 04 '14 at 11:57
  • Ok, I see. I was hoping to get through with couple of encoding/decoding methods, but it's deeper than that. How do I properly register the modified JSONDecoder class so that Flask actually uses it? – Passiday Jun 04 '14 at 12:29
  • 1
    This is not a duplicate of the question linked above -- this question is about storing objects in a Flask session which the Q&A above does not address. – ThatAintWorking Oct 20 '16 at 20:14
  • 2
    Unfortunately for some strange reason the question is marked as a duplicate of a related, but different question, and I can only leave a comment. Anyway, flask requires not only encoding, it also requires decoding to properly store the object. If the class does not provide any vulnerable function (which can be a security issue), we can use `jsonpickle` library to make one line encoding and decoding objects for properly working with them in Flask sessions. – avtomaton Mar 01 '19 at 07:28

1 Answers1

3

You have to create a custom JSONEncoder class and tell Flask to use this for JSON serialization/deserialization.

Here is how it would roughly work:

from flask.json import JSONEncoder

class CustomJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Passport):
            # Implement code to convert Passport object to a dict
            return passport_dict
        else:
            JSONEncoder.default(self, obj)

# Now tell Flask to use the custom class
app.json_encoder = CustomJSONEncoder
jbaiter
  • 6,913
  • 4
  • 30
  • 40
  • 1
    Thanks, this does one half of it. However, for complete solution, I need to hook also decoding. I've found [some example how to do it](http://stackoverflow.com/questions/11507983/how-to-use-jsondecoder-in-python-getting-only-the-inner-dict-to-decode), but it's not Flask-related. I've already found that doing `app.json_decoder = CustomJSONDecoder` doesn't do the trick. – Passiday Jun 04 '14 at 19:34
  • I haven't done that myself, but according to the Flask API docs setting `app.json_decoder` to a sublass of `flask.json.JSONDecoder` should do the trick. What's the precise problem that you have with that? – jbaiter Jun 04 '14 at 23:07
  • I get "itsdangerous.BadPayload" error (BadPayload: Could not load the payload because an exception occurred on unserializing the data). It doesn't help too much in understanding what's wrong. – Passiday Jun 05 '14 at 06:03
  • Put an `import pdb; pdb.set_trace()` inside your deserialization code to find out what is going wrong, looks like you have an error in your deserializer. Maybe also enable debug logging. – jbaiter Jun 05 '14 at 08:57
  • That didn't cause any change in the debug output. The error is not triggered in my code, it's somewhere deeper, in itsdangerous.py. I am starting to feel that hooking up custom JSONEncoder and JSONDecoder is not the right way to live. I think I'll put the code that wraps the passport data encoding/decoding in session variable in a separate class that lives in the application scope and connects to the session when necessary. – Passiday Jun 05 '14 at 09:10