Package: esmerald-simple-jwt
Esmerald Simple JWTThe Simple JWT integration with Esmerald.Documentation:https://esmerald-simple-jwt.dymmond.com📚Source Code:https://github.com/dymmond/esmerald-simple-jwtThe official supported version is always the latest released.This package serves the purpose of facilitating a simple JWT integration of Esmerald and any application
that requires JWT.Based on the standards and security explanations of theofficial documentation,
a simple jwt approach was designed to facilitate the life of the developers andit is 100% custom.Esmerald Simple JWT aims to simplify the generation of theaccess_tokenandrefresh_tokenby
providing out of the box mechanisms and views that can be imported directly into your application.This package uses Pydantic for its own schemas.Installation$pipinstallesmerald-simple-jwtWhat does it bringEsmerald Simple JWT comes with two different ways of using the package.ViaIncludewhere you can simply import directly
the views into your routing system.ViaPluggablewhere the views can be installed into your
application.This is not all what the packages brings for you. It also brings scaffolds for your custom backend
authentication and schemas to represent your token on response. All of this can be found in the
documentation and in more details.How does it workIt is very simple actually. Like everything in Esmerald can be done through thesettings,
this package wouldn't be any different.In a nutshell, you will need to use theSimpleJWTconfiguration provided by
the package inside your settings and then import the urls into your package.MiddlewareThe Esmerald Simple JWTdoes not comewith a middleware for any application and the reason for
this its because you can have your own custom middlewares and your own design without being forced
to use a specific one.QuickstartFor the sake of this example,Edgywill be used as ORM but feel free to
use your own and override anything you want and need.What will we need?AUser model. For this we will be using theEdgy contrib from Esmeraldsince it provides already some out of the box configurations. Feel free to adapt it and use your own
models.Abackend authenticationallowing out user to be validated
for authentication.Abackend refreshthat handles with the refresh token of the user
already logged in.ASimpleJWTconfiguration to be added to the application settings.Both backend and refresh authentication will be using the defaultTokenfrom the
package.The user modelEsmerald provides already some out of the box integrations with databases likeEdgybut the package is not only strict to it. You can change and use whatever it suits you better.This file will be placed in amyapp/apps/accounts/models.py.fromdatetimeimportdatetimefromesmerald.confimportsettingsfromesmerald.contrib.auth.edgy.base_userimportAbstractUser# These configurations are loaded# from the application settings# check the `settings.py`.database,models=settings.db_connectionclassUser(AbstractUser):"""Model using the Esmerald contrib forEdgy and providing useful functionality forany application."""classMeta:registry=modelsThe backend authenticationThebackend authenticationdoes what the names suggests. Validates
and autenticates the user in the system and returns anaccess_tokenandrefresh_token.The backend authentication will be placed inside amyapps/apps/accounts/backends.py.fromdatetimeimportdatetimefromesmerald_simple_jwt.backendsimportBackendEmailAuthenticationasSimpleBackendfromesmerald_simple_jwt.schemasimportTokenAccessfromesmerald_simple_jwt.tokenimportTokenfromedgy.exceptionsimportObjectNotFoundfromesmerald.confimportsettingsfromesmerald.exceptionsimportNotAuthorizedfromesmerald.utils.module_loadingimportimport_stringUser=import_string("accounts.models.User")classBackendAuthentication(SimpleBackend):"""Using the `BackendEmailAuthentication` allows to inheritand use the `email` and `password` fields directly."""asyncdefauthenticate(self)->str:"""Authenticates a user and returns a JWT string"""try:user:User=awaitUser.query.get(email=self.email)exceptObjectNotFound:# Run the default password hasher once to reduce the timing# difference between an existing and a nonexistent user.awaitUser().set_password(self.password)else:is_password_valid=awaituser.check_password(self.password)ifis_password_validandself.user_can_authenticate(user):# The lifetime of a token should be short, let us make 5 minutes.# You can use also the access_token_lifetime from the JWT config directlyaccess_time=datetime.now()+settings.simple_jwt.access_token_lifetimerefresh_time=datetime.now()+settings.simple_jwt.refresh_token_lifetimeaccess_token=TokenAccess(# The `token_type` defaults to `access_token`access_token=self.generate_user_token(user,time=access_time,token_type=settings.simple_jwt.access_token_name,),# The `token_type` defaults to `refresh_token`refresh_token=self.generate_user_token(user,time=refresh_time,token_type=settings.simple_jwt.refresh_token_name,),)returnaccess_token.model_dump()else:raiseNotAuthorized(detail="Invalid credentials.")defuser_can_authenticate(self,user):"""Reject users with is_active=False. Custom user models that don't havethat attribute are allowed."""returngetattr(user,"is_active",True)defgenerate_user_token(self,user:User,token_type:str,time:datetime=None):"""Generates the JWT token for the authenticated user."""ifnottime:later=datetime.now()+settings.simple_jwt.access_token_lifetimeelse:later=timetoken=Token(sub=str(user.id),exp=later)returntoken.encode(key=settings.simple_jwt.signing_key,algorithm=settings.simple_jwt.algorithm,token_type=token_type,)There is a lot to unwrap here right? Well, yes and no.Although it looks very complex, in fact, it
is only using thesimple_jwtsettings to populate the necessary fields and get
some defaults from it such asaccess_token_lifetimeandrefresh_token_lifetimeas well as
the names that will be displayed in the response for the tokens such asaccess_token_nameandrefresh_token_name.The rest is simple python logic to validate the login of a user.The backend refreshThebackend refreshas the name suggests, serves the purpose of
refreshing theaccess_tokenfrom an existingrefresh_tokenonly.TheRefreshAuthenticationon the contrary of the backend authentication, it is already provided
out of the box withinEsmerald Simple JWTbut you don't need to use it as well. Everything
can be customisable for your own needs.The backend refresh will be placed inside amyapps/apps/accounts/backends.pyas well.fromdatetimeimportdatetimefromesmerald_simple_jwt.backendsimportBaseRefreshAuthenticationfromesmerald_simple_jwt.schemasimportAccessToken,RefreshTokenfromesmerald_simple_jwt.tokenimportTokenfromjoseimportJWSError,JWTErrorfromesmerald.confimportsettingsfromesmerald.exceptionsimportAuthenticationError,NotAuthorizedclassRefreshAuthentication(BaseRefreshAuthentication):"""Refreshes the access token given a refresh token of a given user.This object does not perform any DB action, instead, uses the existing refreshtoken to generate a new access."""token:RefreshTokenasyncdefrefresh(self)->AccessToken:token=self.token.refresh_tokentry:token=Token.decode(token=token,key=settings.simple_jwt.signing_key,algorithms=[settings.simple_jwt.algorithm],)except(JWSError,JWTError)ase:raiseAuthenticationError(str(e))fromeiftoken.token_type!=settings.simple_jwt.refresh_token_name:raiseNotAuthorized(detail="Only refresh tokens are allowed.")# Apply the maximum living timeexpiry_date=datetime.now()+settings.simple_jwt.access_token_lifetime# New token objectnew_token=Token(sub=token.sub,exp=expiry_date)# Encode the tokenaccess_token=new_token.encode(key=settings.simple_jwt.signing_key,algorithm=settings.simple_jwt.algorithm,token_type=settings.simple_jwt.access_token_name,)returnAccessToken(access_token=access_token)With the same principle ofbackend authentication, it uses theSimpleJWTconfiguration to populate the default values.The Simple JWT configurationThis is where we assemble the configurations for the package. TheSimpleJWTis
placed inside your application settings file and then used by the application directly.The configuration will be living insidemyapp/configs/settings.py.importosfromfunctoolsimportcached_propertyfromtypingimportOptional,Tuplefromaccounts.backendsimportBackendAuthentication,RefreshAuthenticationfromesmerald_simple_jwt.configimportSimpleJWTfromedgyimportDatabase,RegistryfromesmeraldimportEsmeraldAPISettingsDATABASE_URL=os.environ.get("DATABASE_URI","sqlite:///db.sqlite")classAppSettings(EsmeraldAPISettings):"""The settings object for the application."""@cached_propertydefdb_connection(self)->Tuple[Database,Registry]:"""This conenction is used in `myapp/apps/accounts/models.py."""database=Database(DATABASE_URL)returndatabase,Registry(database=database)@propertydefsimple_jwt(self)->SimpleJWT:returnSimpleJWT(signing_key=self.secret_key,backend_authentication=BackendAuthentication,backend_refresh=RefreshAuthentication,)Did you see how simple it was? Basically you just need to implement your own backend and refresh
backends and then import them into theSimpleJWTconfiguration.!!! Danger
The settingsmust be calledsimple_jwtor the application will fail to use the
Esmerald Simple JWT package.Use the Esmerald Simple JWTNow it is time to assemble the application and use the package.As mentioned at the beginning, there are two different ways.ViaIncludewhere you can simply import directly
the views into your routing system.ViaPluggablewhere the views can be installed into your
application.Via IncludeThis is the simplest approach to almost every application in Esmerald.fromesmeraldimportEsmerald,Includeapp=Esmerald(routes=[Include(path="/auth",namespace="esmerald_simple_jwt.urls"),])Via PluggableThis is the other way that Esmerald allows you to extend functionality. The pugglable will simply
install the package inside your application.fromesmerald_simple_jwt.extensionimportSimpleJWTExtensionfromesmeraldimportEsmerald,Pluggableapp=Esmerald(pluggables={"simple-jwt":Pluggable(SimpleJWTExtension,path="/auth"),},)Starting and accessing the viewsWith everything assembled, we can now start our application but beforewe need to tell the application to use our custom settings.$exportESMERALD_SETTINGS_MODULE=myapp.configs.settings.AppSettingsYou can now start the application and access the endpoints viaPOST./auth/signin- The login view to generate theaccess_tokenand therefresh_token./auth/refresh-access- The refresh view to generate the newaccess_tokenfrom therefresh_token.OpenAPIWhen Esmerald Simple JWT is added into your application, unless specified not to, it will add the
urls automatically to your OpenAPI documentation and so you can also access them via:/docs/swagger- The default OpenAPI url for the documentation of any Esmerald application.