Protecting Routes

The purpose of this package, beyond the creation of JWTs, is to protect routes so that only users with a valid access token can reach certain resources. Endpoints in your application can be protected using the @protected decorator.


The @protected decorator

Purpose: To protect an endpoint from being accessed without a valid access token.
Example:

from sanic_jwt.decorators import protected


@app.route("/")
async def open_route(request):
    return json({"protected": False})


@app.route("/protected")
@protected()
async def protected_route(request):
    return json({"protected": True})

Now, anyone can access the / route. But, only users that pass a valid access token can reach /protected.

If you have initialized Sanic JWT on a Blueprint, then you will need to pass the instance of that blueprint into the @protected decorator.

bp = Blueprint('Users')
Initialize(bp, app=app)

@bp.get('/users/<id>')
@protected(bp)
async def users(request, id):
    ...

Alternatively (and probably preferably), you can also access the decorator from the Initialize instance. This makes it easier if you forget to pass the Blueprint.

bp = Blueprint('Users')
sanicjwt = Initialize(bp, app=app)

@bp.get('/users/<id>')
@sanicjwt.protected()
async def users(request, id):
    ...

Class based views

Using the standard Sanic methodology, you can protect class based views with the same decorator.

class PublicView(HTTPMethodView):
def get(self, request):
    return json({"protected": False})


class ProtectedView(HTTPMethodView):
    decorators = [protected()]

    async def get(self, request):
        return json({"protected": True})

app.add_route(PublicView.as_view(), '/')
app.add_route(ProtectedView.as_view(), '/protected')

Passing the Token

There are two general methodologies for passing a token: cookie based, and header based. By default, Sanic JWT will expect you to send tokens thru HTTP headers.

curl -X GET -H "Authorization: Bearer <JWT>" http://localhost:8000/auth/me

Header Tokens

Header tokens are passed by adding an Authorization header that consists of two parts:

  1. the word Bearer
  2. the JWT access token

If you would like, you can modify this behavior by changing the configuration for authorization_header and authorization_header_prefix.

Initialize(
    app,
    authorization_header='somecustomheader',
    authorization_header_prefix='MeFirst',)
curl -X GET -H "somecustomheader: MeFirst <JWT>" http://localhost:8000/auth/me

Query String Tokens

Sometimes, both header based authentication and cookie based authentication will not be enough. A third option is available to look for tokens inside query string arguments:

http://localhost?access_token=<JWT>

This can be enabled with query_string_set=True. One potential use for this would be authentication of a websocket endpoint where sending headers and cookies may be more challenging due to Javascript limitations.

Warning

In most scenarios, it is advisable to not use query strings for authentication. One of the biggest reasons is that the tokens may be easily leaked if a URL is copied and pasted, or because the token may end up in server logs. However, the option is available if you need it and you feel comfortable that you can mitigate any risks.

Per view declaration

Perhaps you realize that you would like to make the declaration on a single view? Most of your views will operate using a cookie, but one particular endpoint (for whatever reason) will best be served to accept headers. Not a problem. You can simply pass in the configuration parameters right into the decorator!

Initialize(
    app,
    cookie_set=True,
    cookie_strict=False,)

@app.route("/protected_by_header")
@protected(cookie_set=False)
async def protected_by_header_route(request):
    ...

Learn more about configuration overrides.

Note

This paradigm works for all configurations. Feel free to experiment and change config settings at the lowest level you might need them.


Advanced Decorators

Want to have a greater level of control? Instead of just importing the decorators from the sanic_jwt.decorators module, you can also use the decorator directly off your initialized Sanic JWT instance!

sanicjwt = Initialize(app)

@app.route("/protected")
@sanicjwt.protected()
async def protected_route(request):
    ...

This also works for blueprints (and has the added advantage that you no longer need to pass the bp instance in.

bp = Blueprint('Users')
Initialize(bp, app=app)

@bp.get('/users/<id>')
@bp.protected()
async def users(request, id):
    ...

Note

This concept of having instance based decorators also works for the scoped decorator: bp.scoped('foobar'); and the @inject_user decorator.

@inject_user decorator

You’ve gone thru the hard work and added a retrieve_user method. You might as well be able to reap the benefits by leveraging that method to inject your user data into your endpoints.

@app.route("/protected/user")
@inject_user()
@protected()
async def my_protected_user(request, user):
    return json({"user_id": user.user_id})