Authentication is an essential part of most applications. It refers to the methods and techniques used to verify the identity of users interacting with your application. There are many different approaches and strategies to handle authentication. The approach taken for any project depends on its particular application requirements. In this section, we shall go through different approaches to authentication in Ellar and how it will suit your authentication requirements.
There are two ways in which user authentication and identification are processed in Ellar:
We have discussed in detail how Guards are used to protect a route and check for user authorizations, but we never really addressed how they can be used for authentication purposes. For this, we are going to illustrate JWT authentication using Guard
Let's flesh out our requirements. For this use case, clients will start by authenticating with a username and password. Once authenticated, the server will issue a JWT that can be sent as a bearer token in an authorization header on subsequent requests to prove authentication. Then, we create a protected route that is accessible only to requests that contain a valid JWT.
Let's start with the first requirement: authenticating a user, then extend that by issuing a JWT. And finally, we'll create a protected route that checks for a valid JWT on the request.
We´ll start by scaffolding an AuthModule with the Ellar CLI tool followed by AuthService and AuthController implementations. We´ll use the AuthService to implement the authentication logic and the AuthController to expose the authentication endpoints.
ellarcreate-moduleauth
Also, the AuthService would need UserService, which encapsulates user operations. Let's also scaffold a user module.
ellarcreate-moduleuser
Now, let's add some implementations to the generated files. For this application, the UserService will be working with a hard-coded list of users with a retrieve one-by-email method. In a real application, you´d build your user model and persistence layer using a library of your choice like SQLAlchemy, Django ORM, Peewee, PonyORM, etc.
In the above example, we have used make_password to hash the password. It is strictly advised you don't save passwords as plain text. In the UsersModule, we need to register the UserService we just created so that it will be injectable in AuthService
user/module.py
1 2 3 4 5 6 7 8 91011121314
fromellar.commonimportModulefromellar.coreimportModuleBasefrom.servicesimportUsersService@Module(providers=[UsersService],exports=[UsersService])classUserModule(ModuleBase):""" User Module """
At this junction, the AuthService returns a Python dictionary object of the user retrieved if the password is correct. But in the real sense, we need a token returned to the client.
For this, we need to install the ellar-jwt package
pipinstallellar-jwt
Let us review and refine our requirements once again:
Allow users to authenticate with username/password, returning a JWT for use in subsequent calls to protected API endpoints. This is almost done. What is left is to write the code that issues a JWT.
Create API routes that are protected based on the presence of a valid JWT as a bearer token
EllarJWT comes with JWTModule and JWTService for encoding and decoding tokens. Let us configure the JWTModule inside AuthModule.
In the above example, we configured JWTModule with very minimal configurations and registered it as a module dependency together with UserModule. Also we have registered AuthController and AuthService to AuthModule as well. With that done, we have completed the AuthModule setup.
Now, let us finish the AuthService by returning a token using JWTService.
At this point, we can now comfortably address our final requirement: protecting endpoints by requiring a valid JWT to be present on the request. We will do this by creating an AuthGuard that will be used to guard our routes.
Ensure the app is running, and test the routes using cURL.
$# GET /auth/profile
$curlhttp://localhost:8000/auth/profile
{"detail":"Forbidden"}# status_code=403
$# POST /auth/login
$curl-XPOSThttp://localhost:8000/auth/login-d'{"username": "john", "password": "password"}'-H"Content-Type: application/json"{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTg3OTE0OTE..."}
$# GET /profile using access_token returned from previous step as bearer code
$curlhttp://localhost:8000/auth/profile-H"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm..."{"exp":1698793558,"iat":1698793258,"jti":"e96e94c5c3ef4fbbbd7c2468eb64534b","sub":1,"user_id":1,"username":"john","id":null,"auth_type":"bearer"}
Note in the AuthModule configuration, we configured the JWT to have an expiration of 5 minutes. If you wait 5 minutes after authenticating before attempting a GET/auth/profile request, you'll receive a 401 Unauthorized response. This is because the EllarJWT package automatically checks the JWT for its expiration time, saving you the trouble of doing so in your application.
This was not included in the requirement, but it might be useful to some developers. So, let us address token refresh using EllarJWT. Depending your application, this illustration may vary.
To get this done, we need to edit the sign_in in AuthService to return access_token and refresh_token. We also need to add a refresh_token endpoint to our AuthController.
We have modified the sign_in method and added the refresh_token method to handle refresh token actions. The sign_in method return access_token and refresh_token that expires in 30days.
In situations when you need to protect all your endpoints, you can register AuthGuard as a global guard instead of using the @UseGuards decorator in all your controllers or route functions. However, you also need to implement a mechanism to skip the auth guard for certain route functions that don't require it, such as the sign_in route function.
First, let us register AuthGuard a global guard in AuthModule.
Let us define a mechanism for declaring routes as anonymous or public.
One way to achieve this, is by using the set_metadata decorator. We can set some metadata on those functions, and it can be read in AuthGuard. If the metadata is present, we exit the authentication verification and allow the execution to continue.
We have defined the allow_any metadata decorator in the above illustration and have used Ellar's built-in class Reflector to read the metadata defined at the controller or route function.
We can create an allow_any decorator function that defines a guard metadata on the decorated function to override the global guard