API Development
Adding API Support to your Masonite project is very simple. Masonite comes with supporting for returning JSON responses already but there are a few API features for authenticating and authorizing that are helpful.
Default projects don't come with these features so you'll have to simply register them.
First, register the ApiProvider in your project by adding the service provider to your PROVIDERS list:
from masonite.api.providers import ApiProvider
PROVIDERS = [
#..
ApiProvider
]
This will register some required classes used for API development.
You should now create an
api.py
file for adding your API routes to:# routes/api.py
ROUTES = [
Route.get('/users', 'UsersController@index')
]
You will then have to add a binding to the container for the location of this file. You can do so in your
Kernel.py
file inside the register_routes
method:def register_routes(self):
#..
self.application.bind("routes.api.location", "routes/api")
Any routes inside this file will be grouped inside an api middleware stack.
Next, you must choose a model you want to be responsible for authentication. This is typically your User model. You will have to inherit the
AuthenticatesTokens
class onto this model:from masoniteorm.models import Model
from masonite.api.authentication import AuthenticatesTokens
class User(Model, AuthenticatesTokens):
#..
This will allow the model to save tokens onto the table.
Next you will add a column to the models table for saving the token. Here we are naming it
api_token
but this is configurable by adding a __TOKEN_COLUMN__
attribute to your model. Your migration file should look like this:with self.schema.table("users") as table:
table.string("api_token").nullable()
Then migrate your migrations by running:
$ python craft migrate
Next, you can create a new API config file. You can do so simply by running the install command:
python craft api:install
This will create a new
api.py
config file in your configuration directory that looks like this:"""API Config"""
from app.models.User import User
from masonite.environment import env
DRIVERS = {
"jwt": {
"algorithm": "HS512",
"secret": env("JWT_SECRET"),
"model": User,
"expires": None,
"authenticates": False,
"version": None,
}
}
This will attempt to import your user model but if you have a different model or if its in a different location you can change it in that model.
This command will also generate a secret key, you should store that secret key in an environment variable called
JWT_SECRET
. This will be used as a salt for encoding and decoding the JWT token.The
authenticates
key is used as a check to check against the database on every request to see if the token is set on the user. By default, the database is not called to check if the token is assigned to a user. One of the benefits of JWT is the need to not have to make a database call to validate the user but if you want that behavior, you can set this option to True
You should add some routes to your
web.py
file which can be used to authenticate users to give them JWT tokens:from masonite.api import Api
ROUTES += [
#.. web routes
]
ROUTES += Api.routes(auth_route="/api/auth", reauth_route="/api/reauth")
The above parameters are the defaults already so if you don't want to change them then you don't have to specify them.
Since the routes in your
api.py
file are wrapped in an api
middleware, you should add a middleware stack in your route middleware in your Kernel file:# Kernel.py
from masonite.api.middleware import JWTAuthenticationMiddleware
#..
class Kernel:
# ..
route_middleware = {
# ..
"api": [
JWTAuthenticationMiddleware
],
}
This middleware will allow any routes set on this stack to be protected by JWT authorization.
By default, all routes in theroutes/api.py
file already have theapi
middleware stack on them so there is no need to specify the stack on all your API routes.
Once these steps are done, you may now create your API's
Once the setup is done, we may start building the API.
One of the ways to build an API is using controller and route resources.
A controller resource is a controller with several methods on them used to specify each action within an entity in the application (like users).
To create a controller resource you can run the controller command with an
-a
flag:$ python craft controller api/UsersController -a
This will create a controller with the following methods:
- index
- show
- store
- update
- destroy
You can then create a route resource:
# routes/api.py
from masonite.routes import Route
ROUTES = [
# ..
Route.api('users', "api.UserController")
]
This will create the below routes which will match up with your API Controller methods:
Method | URL | Action | Route Name |
---|---|---|---|
GET | /users | index | users.show |
GET | /users/@id | show | users.show |
POST | /users | store | users.store |
PUT/PATCH | /users/@id | update | users.update |
DELETE | /users/@id | destroy | users.destroy |
The routes we added earlier contain 2 authentication methods. The
/api/auth
route can be used to get a new authentication token:First, send a
POST
request with a username
and password
to get back a JWT token:{
"data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHBpcmVzIjpudWxsLCJ2ZXJzaW9uIjpudWxsfQ.OFBijJFsVm4IombW6Md1RUsN5v_btPwl-qtY-QSTBQ0b7N2pca8BnnT4OjfXOVRrKCWaKUM3QsGj8zqxCD4xJg"
}
You should then send this token with either a
token
input or a Authorization
header:Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHBpcmVzIjpudWxsLCJ2ZXJzaW9uIjpudWxsfQ.OFBijJFsVm4IombW6Md1RUsN5v_btPwl-qtY-QSTBQ0b7N2pca8BnnT4OjfXOVRrKCWaKUM3QsGj8zqxCD4xJg
If you do not set a value for the
expires
key in the configuration file then your JWT tokens will not expire and will be valid forever.If you do set an expiration time in your configuration file then the JWT token will expire after that amount of minutes. If the token expires, you will need to reauthenticate to get a new token. This can be done easily by sending the old token to get back a new one:
You can do this by sending a POST request to
/api/reauth
with a token
input containing the current JWT token. This will check the table for the token and if found, will generate a new token.One of the issues with JWT tokens is there is little that can be done to invalidate JWT tokens. Once a JWT token is valid, it is typically valid forever.
One way to invalid JWT tokens, and force users to reauthenticate, is to specify a version. A JWT token authenticated will contain a version number if one exists. When the JWT token is validated, the version number in the token will check against the version number in the configuration file. If they do not match then the token is considered invalid and the user will have to reauthenticate to get back a new token.
Since we store the active api_token on the table we are able to retrieve the user using the
LoadUserMiddleware
and a new guard
route middleware stack:First add the guard middleware stack and add the
LoadUserMiddleware
to the api
stack.##..
"api": [
JWTAuthenticationMiddleware,
LoadUserMiddleware
],
"guard": [
GuardMiddleware
],
Lastly, in your route or route groups you can specify the guard middleware and specify the guard name:
# api.py
ROUTES = [
#..
Route.get('/users', 'UserController@show').middleware("guard:jwt")
]
Last modified 2d ago