Endpoints and Responses¶
Sanic JWT sets itself up to run as a Sanic Blueprint at the /auth
path.
# Default
http://localhost:8000/auth
This is can be changed via the url_prefix
setting. See settings for more.
Initialize(app, url_prefix='/api/authentication')
All Sanic JWT endpoints will now be available at:
# Custom
http://localhost:8000/api/authentication
Default Endpoints¶
By default, there are four endpoints that ship with Sanic JWT. You can change the path that they attach to by following configuration pattern below:
Initialize(
app,
path_to_authenticate='/my_authenticate',
path_to_retrieve_user='/my_retrieve_user',
path_to_verify='/my_verify',
path_to_refresh='/my_refresh',
)
Authenticate¶
/auth
POST
authenticate
method is truthy.Request
curl -X POST -H "Content-Type: application/json" -d '{"username": "<USERNAME>", "password": "<PASSWORD>"}' http://localhost:8000/auth
Response
200 Response
{
"access_token": "<JWT>"
}
Verification¶
/auth/verify
GET
Request
curl -X GET -H "Authorization: Bearer <JWT>" http://localhost:8000/auth/verify
Response
200 Response
{
"valid": true
}
## or
400 Response
{
"valid": false,
"reason": "Signature has expired"
}
Current User Details¶
/auth/me
GET
Request
curl -X GET -H "Authorization: Bearer <JWT>" http://localhost:8000/auth/me
Response
200 Response
{
"me": {
user_id": 123456
}
}
Note
Because this package does not know about you user management layer, you need to have a user object that either is a dict
or a python object instance with a to_dict()
method. The output of these methods will be used to generate the /me
response.
Refresh Token¶
/auth/refresh
POST
Request
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer <JWT>" -d '{"refresh_token": "<REFRESH TOKEN>"}' http://localhost:8000/auth/refresh
Response
{
"access_token": "<JWT>"
}
Note
Do not forget to supply an existing access_token
. Even if it is expired, you must send the token along so that the application can get the user_id
from the token’s payload and cross reference it with the refresh_token
. Think of it as an additional level of security. To understand why, checkout Issue #52.
Modify Responses¶
The responses for each of the default endpoints is extendable by subclassing the Responses
class, and hooking into the appropriate method. Just make sure you return a dict
.
Your custom Responses
should be hooked up to Sanic JWT using the responses_class
keyword argument on the Initialize
instance.
from sanic_jwt import Responses
class MyResponses(Responses):
@staticmethod
def extend_authenticate(request,
user=None,
access_token=None,
refresh_token=None):
return {}
@staticmethod
def extend_retrieve_user(request, user=None, payload=None):
return {}
@staticmethod
def extend_verify(request, user=None, payload=None):
return {}
@staticmethod
def extend_refresh(request,
user=None,
access_token=None,
refresh_token=None,
purported_token=None,
payload=None):
return {}
Initialize(app, responses_class=MyResponses)
Custom Endpoints¶
Sometimes you may find the need to add another endpoint to your authentication system. You can do this by hooking it up at initialization.
from sanic_jwt import BaseEndpoint
class MyEndpoint(BaseEndpoint):
...
my_views = (
('/my-view', MyEndpoint),
)
Initialize(app, class_views=my_views)
Example:
What if we wanted a /register
endpoint? It could easily be added like this:
from sanic_jwt import BaseEndpoint
class Register(BaseEndpoint):
async def post(self, request, *args, **kwargs):
username = request.json.get('username', None)
email = request.json.get('email', None)
helper = MyCustomUserAuthHelper()
user = helper.register_new_user(username, email)
access_token, output = await self.responses.get_access_token_output(
request,
user,
self.config,
self.instance)
refresh_token = await self.instance.auth.get_refresh_token(request, user)
output.update({
self.config.refresh_token_name(): refresh_token
})
response = self.responses.get_token_reponse(
request,
access_token,
output,
refresh_token=refresh_token,
config=self.config)
return response
my_views = (
('/register', Register),
)
# Please note that this Initialize instance is incomplete.
# It is missing handlers for: authenticate, store_refresh_token, and
# retrieve_refresh_token.
# It is meant as illustrative purposes on how you might approach this.
# See https://github.com/ahopkins/sanic-jwt/issues/111 for more information.
Initialize(app, class_views=my_views, ...)
You hook up your custom endpoints at initialization by providing Initialize
with a class_views
argument naming your endpoint and its path.
my_endpoints = (
('/path/to/endpoint', MyCustomClassBasedView)
)
Note
It must be a class based view. While it is certainly possible to subclass Sanic’s sanic.views.HTTPMethodView
, it is recommended that you subclass sanic_jwt.BaseEndpoint
instead so you have access to:
self.instance
(the current Sanic JWT),self.config
(all current configurations), andself.responses
(the current response class instance).
Exception Handling¶
You can customize how Sanic JWT handles responses on an exception by subclassing the Responses
class, and overriding exception_response
.
from sanic_jwt import Responses
class MyResponses(Responses):
@staticmethod
def exception_response(request, exception):
exception_message = str(exception)
return json({
'error': True,
'message': f'You encountered an exception: {exception_message}'
}, status=exception.status_code)
Initialize(app, response_class=MyResponses)
Microservices¶
One of the benefits of a lightweight framework like Sanic is that it makes building microservice architectures simple, and flexible. If you are building a microservice application, likely you do not want all of your services to have the /auth
endpoints!
Instead, you probably only want to authenticate against a single service, and use the token generated there among all your services. This can be easily accomplished with the auth_mode=True
Configuration. Set it to True
on your authentication service, and False
everywhere else. All the decorators will still work as expected.
# Authentication service
Initialize(app, authenticate=lambda: True)
# Every other service
Initialize(app, auth_mode=False)
Now, the /auth
endpoints are only on your authentication service, but the access token can be used on ANY of your other services.
Note
This works only if each of the services has the same secret
.