Don't async past me... Await!

Problem

The following code will (perhaps unexpectedly) let anyone access the functionality intended only for authenticated users

async def is_authenticated(user):
    if user == 'admin':
        return True
    return False

def do_stuff_that_requires_auth():
    print("I'm authenticated")

def main():
    user = 'not-an-admin'
    if not is_authenticated(user):
        print("You do not have permission for this")
        return
    do_stuff_that_requires_auth()

if __name__ == '__main__':
    main()

If we run it we’ll get:

<input>:11: RuntimeWarning: coroutine 'is_authenticated' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
I'm authenticated

At first glance it might not be obvious why this is, it’s because is_authenticated('anything') returns a coroutine object which is “true” i.e.

bool(is_authenticated('fake_user')) is True
<input>:1: RuntimeWarning: coroutine 'is_authenticated' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
True

Solution

The RuntimeWarning’s give a clue about how to fix it, we need to await the coroutine result, which in tern means that main() needs to become an async function too. Now main being async itself means that we need to call it in a different way:

import asyncio

async def is_authenticated(user):
    if user == 'admin':
        return True
    return False

def do_stuff_that_requires_auth():
    print("I'm authenticated")

async def main():
    user = 'not-an-admin'
    if not await is_authenticated(user):
        print("You do not have permission for this")
        return
    do_stuff_that_requires_auth()

if __name__ == '__main__':
    asyncio.run(main())
You do not have permission for this

See the Python Docs on Coroutines and Tasks for more details