Sanic-jwt retrieve user

Hey ya

I’ve been implementing sanic-jwt around my api end points and have the initial authentication working against the user records in my mongodb. I think i’ve probably set something up wrong though (I’m a python amateur and reasonably new to sanic), as if i try auth/me i’m not able to retrieve the user info as there is no user_id in the payload. Its value is just None.

I’m initialising here:

initialize_jwt(app, authenticate=authenticate, retrieve_user=retrieve_user, access_token_name='token')

Authentication:

async def authenticate(request, *args, **kwargs):

username = request.json.get("username", None)
password = request.json.get("password", None)

if not username or not password:
    raise exceptions.AuthenticationFailed("Missing username or password.")

user_doc = await request.app.db['users'].find(
    {"contact_details.email": username}).to_list(None)

if len(user_doc) == 0:
    raise exceptions.AuthenticationFailed("User not found.")

if not validate_password(password, user_doc[0]['password']):
    raise exceptions.AuthenticationFailed("Password is incorrect.")

return user_doc[0]

Retrieve user:

async def retrieve_user(request, payload, *args, **kwargs):
if payload:
    user = await request.app.db['users'].find(
        {"_id": payload['user_id']}).to_list(None)
    return user[0]
else:
    return None

Once i get this working i want to add scopes (which i currently think will suffer the same issue i have here currently).

Thanks in advance for any guidance/pointers

After you get a token back, what are you getting for a payload? Can you post it here?

What is the general schema of your user object in Mongo dB? How do you access the user ID on the object?

Will be happy to help you get this setup if you need thoughts or advice.

Hi @ahopkins, thanks for the quick reply. My user object looks like this:

{"_id":"string","name":{
  "first":"string",
  "last":"string"},"contact_details":{
  "email":"string",
  "phone":"string",
  "address":"string"}, "password":{
  "hash":"string",
  "salt":"string" }}

With the record retrieved from the database the ID is accessed by just specifying the _id key.

After i get the token back the payload when i then try and hit auth/me looks like:

{'user_id': None, 'exp': 1577764315}

cheers

ok, so i’ve got this working now. As sanic-jwt is looking for the key of user_id in the user object i’m modifying my current object before returning it. Which has done the trick.

My authenticate function now looks like this:

async def authenticate(request, *args, **kwargs):
username = request.json.get("username", None)
password = request.json.get("password", None)

if not username or not password:
    raise exceptions.AuthenticationFailed("Missing username or password.")

user_doc = await request.app.db['users'].find(
    {"contact_details.email": username}).to_list(None)

if len(user_doc) == 0:
    raise exceptions.AuthenticationFailed("User not found.")

if not validate_password(password, user_doc[0]['password']):
    raise exceptions.AuthenticationFailed("Password is incorrect.")

return {**user_doc[0], 'user_id': str(user_doc[0]['_id'])}

and retrieve user:

async def retrieve_user(request, payload, *args, **kwargs):
if payload:
    user = await request.app.db['users'].find(
        {"_id": ObjectId(payload['user_id'])}).to_list(None)
    return json.loads(json.dumps(user[0], default=str))
else:
    return None

Will be taking a look at scopes tomorrow

ps. How do you flag code insertion properly on this forum so it formats correctly?

d’oh, just seen in the docs that a key override can be passed when initiating the auth, so i’ll just go ahead and do that now :man_facepalming:

Got scoping working as well, thanks to your clear docs (which i obviously should have read better in the first place)

1 Like

:sunglasses: Glad you figured it out. I was going to suggest the configuration change. For the sake of anyone else that lands here in the future: https://sanic-jwt.readthedocs.io/en/latest/pages/configuration.html#user-id

1 Like

In regards to scopes, I broke this part off into its own package. Sanic JWT does not yet use this, but probably will in the near future. It works pretty much the same, but has some additional features.