Start learning free

LESSON 90 OF 188 · BACKEND: USERS AND SECURITY

A login_required Decorator

Practice

Every page for logged-in users starts the same way: find the user from the token, and answer 401 when there is none. Copying those lines into every route works until the day you forget them on one route, and that route is open to everybody. A decorator writes the check once, and one line puts it in front of any route.

A decorator is a function that receives a route's function and gives back another function, a wrapper, to use in its place. You already use one: @app.get. Here is a small one that has nothing to do with logins: it prints the name of each route before running it.

def log_calls(view):
    @wraps(view)
    def wrapper(**kwargs):
        print("calling", view.__name__)
        return view(**kwargs)
    return wrapper

@app.get("/hello")
@log_calls
def hello():
    return "Hello!"

Flask calls a route's function with the values from its path as named arguments, like product_id=2; **kwargs collects them all, so one wrapper fits every route. Flask also tells routes apart by the name of their function: @wraps(view), from functools, copies the route's name onto the wrapper. Without it, every protected route would be called wrapper and Flask would stop with an error.

Ana (id 1) signs up and logs in first; <token> is the token that login answered:

POST /register  {"email": "ana@example.com", "password": "secret123"}  →  201  {"email": "ana@example.com", "id": 1}
POST /login     {"email": "ana@example.com", "password": "secret123"}  →  200  {"token": "<token>"}
GET /me         Authorization: Bearer <token>  →  200  {"email": "ana@example.com", "id": 1}
GET /me         (no header)                    →  401  {"error": "Login required"}
GET /welcome    Authorization: Bearer <token>  →  200  Welcome back, ana@example.com!
GET /welcome    (no header)                    →  401  {"error": "Login required"}
GET /welcome    Authorization: Bearer 0123456789abcdef0123456789abcdef  →  401  {"error": "Login required"}

Your task: below current_user, write the decorator login_required: its wrapper answers 401 "Login required" when current_user() finds nobody, and otherwise calls the route with the user. Put it on /me, whose function now receives user, and add a protected GET /welcome that answers the text Welcome back, <email>!.


Assignment
Follow the Coding Tutorial to write the login_required decorator and protect /me and /welcome with it.


Hint
There are two returns: wrapper returns view(user, **kwargs), and login_required returns wrapper itself, without parentheses. Put @login_required between @app.get(...) and the def.


STEP 1 OF 12
    Ctrl + Enter to check