Package: pyjwt-wrapper
An easy to use wrapper aroundPyJWTfor authentication and authorization.Why this may be usefulQuick StartInstallationImplement your ownBackEndAuthenticatorAuthenticating a user that supplies a username and passwordAuthorize an API request using theaccess_tokenfrom AuthenticationImportant ParametersUsing the password saltUsing the JWT secretUsing the Refresh TokenImplementationAuthenticationThe JSON Web Token (generated frompyjwt_wrapper.authentication)Access TokenUser TokenAuthorization (as implemented by frompyjwt_wrapper.authorization)LoggingTesting from SourceTo DoNote: The development is still in an early stage, and therefore documentation should improve over time. Right now, only some basic concepts are covered.Why this may be usefulPyJWT is a really solid library and a very useful tool for creating and using JSON Web tokens (JWT) in applications. Check outhttps://jwt.io/for more info around JSON Web Tokens.This library is a wrapper around PyJWT that creates a standardaccess tokenanduser token.Note: Refresh tokens to come soon...Quick StartInstallationpipinstallpyjwt-wrapperPyPi page:https://pypi.org/project/pyjwt-wrapper/Implement your ownBackEndAuthenticatorBelow is a quick example of how you would implement your own password based authentication. In stead of users being in a dictionary, you would typically connect to a database.Note: The example below assumes the password is encoded using SHA256. However, when a user enters a password, we will receive it in normal text format which means it also needs to be SHA256 converted in order to compare to the password on record.You would typically implement in this code in the application/API that handles authentication.frompyjwt_wrapperimportLogger,BackEndAuthenticator,AuthenticationResult,generate_jwt,PASSWORD_SALTimporttracebackimporthashlibclassMyBackEndAuthenticator(BackEndAuthenticator):def__init__(self,logger:Logger=Logger()):super().__init__(logger=logger)defauthenticate(self,input:dict,request_id:str=None)->AuthenticationResult:my_hard_coded_users={'user1':{'password':hashlib.sha256('!paSsWord123!{}'.format(PASSWORD_SALT).encode('utf-8')).hexdigest(),'permissions':['p1','p2'],'active':True}}result=AuthenticationResult(success=False,userid=None,permissions=list())try:input_password_hashed=hashlib.sha256('{}{}'.format(input['password'],PASSWORD_SALT).encode('utf-8')).hexdigest()ifinput['username']inmy_hard_coded_users:ifmy_hard_coded_users[input['username']]['active']:ifinput_password_hashed==my_hard_coded_users[input['username']]['password']:result.success=Trueresult.userid=input['username']result.permissions=my_hard_coded_users[input['username']]['permissions']self.logger.info(message='LOGIN SUCCESS for user "{}"'.format(input['username']),request_id=request_id)else:self.logger.error(message='LOGIN FAIL for user "{}" - incorrect password'.format(input['username']),request_id=request_id)else:self.logger.error(message='LOGIN FAIL for user "{}" - user not active'.format(input['username']),request_id=request_id)else:self.logger.error(message='LOGIN FAIL for user "{}" - user not found'.format(input['username']),request_id=request_id)except:self.logger.error(message='EXCEPTION:{}'.format(traceback.format_exc()),request_id=request_id)returnresultAuthenticating a user that supplies a username and passwordYou would also implement in this code in the application/API that handles authentication.frompyjwt_wrapper.authenticationimportauthenticate_using_user_credentials# This part would tyically be implemented in a function that receives the username and passwordresult=authenticate_using_user_credentials(application_name='my_awesome_app',username='user1',password='!paSsWord123!',request_id='test123',backend=MyBackEndAuthenticator())# Return the result to the client...The result may look something like this:{'user_token':'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteV9hd2Vzb21lX2FwcCIsInN1YiI6InVzZXIxIiwiY29udGV4dCI6Im15X2F3ZXNvbWVfYXBwIn0.qylm2cpukiUzCAjeDhO99iTMAWwdjuJKt4Jb2q0np2A','access_token':'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteV9hd2Vzb21lX2FwcCIsInN1YiI6InVzZXIxIiwiYXVkIjoibXlfYXdlc29tZV9hcHAiLCJleHAiOjE2MTYzMTg1MTYsIm5iZiI6MTYxNjIzMjExNiwiaWF0IjoxNjE2MjMyMTE2LCJqdGkiOiJ0ZXN0MTIzIiwicHJtIjpbInAxIiwicDIiXX0.3UXG_fasgaj88ujbltHKqNUgJA1DJX9O6C6-i0Y1cIU','request_id':'test123'}You should then use theaccess_tokenin every API call you make.Authorize an API request using theaccess_tokenfrom AuthenticationThe authorization code is implemented in applications/API that receives requests from the user, which is why each request must include theaccess_token.frompyjwt_wrapper.authorizationimportauthorize_tokenauthorized=authorize_token(token=result['access_token'],application_name='my_awesome_app',request_id='api123')The resulting value fromauthorizedshould beTrueImportant ParametersThere are two parameters that are important to take note of:PASSWORD_SALT- The salt used for added protection when storing passwords.JWT_SECRET- The secret required to encrypt and validate a JWTUsing the password saltDepending on yourBackEndAuthenticatorimplementation, thePASSWORD_SALTmay be considered optional. It's provided as a convenient way to include the parameter.When your user register, your application should add some salt to ensure the password is even better protected when storing it in a database.First, in the environment your application run, ensure thePASSWORD_SALTis set as an environment variable. This can be done using the following shell command on Linux like operating systems:exportPASSWORD_SALT="some really random text and special characters."Typically, you will salt the password with something like:user_password='....'# Store the user's password in a variablehashed_password=hashlib.sha256('{}{}'.format(user_password,PASSWORD_SALT).encode('utf-8')).hexdigest()# You can now save the hashed_password in a databaseIf you want to generate a strong salt once-off, you can run the following shell command:python3-c"from pyjwt_wrapper import generate_random_string; print('{}'.format(generate_random_string(length=60)))"Important: You need to safely and securely store your salt value as any password validation will be rendered useless if you don't have the original salt it was hashed withUsing the JWT secretThe JWT secret is required and as with the password salt, you need to store it safely and securely. If you ever need to change the secret, any existing tokens created with the previous secret will not pass validation and all users would have to re-authenticate to get new tokens issued with the new secret.Again, you can use the following one-liner utility to generate the secret:python3-c"from pyjwt_wrapper import generate_random_string; print('{}'.format(generate_random_string(length=60)))"Set the following environment variable in your application environment:exportJWT_SECRET="some really random text and special characters."Using the Refresh TokenIf your front-end application gets an authorization error and you have a refresh token, you need to use the refresh token to get another set of access and refresh tokens.Only once the refresh token have also reached expiry, must the application re-authenticate the user to get a fresh set of tokens. Form the front-end, this action can be triggered when both an API request and a refresh tokens request returned an error.The refresh token by default only does the following basic checks in order to decide if the access token can be refreshed:Validate the supplied access token checksum is equal to the original calculated checksumValidate that the refresh token has not expiredIf you need to do some additional validation, for example checking a back-end to see that the user account is still active, you can implement a function to do tat and pass it to therefresh_tokensfunction as a parameter. Here is an example:frompyjwt_wrapper.authenticationimportrefresh_tokensimporttracebackdefmy_validation_function(*args)->bool:final_result=False# fail safelyapplication_name=args[9]decoded_access_token=args[1]decoded_refresh_token=args[2]logger=args[3]request_id=args[4]secret_str=args[5]try:# Do your own checks here....passexcept:logger.error(message='EXCEPTION:{}'.format(traceback.format_exc()),request_id=request_id)returnfinal_result# Later, call the refresh tokens function.## In this example, if the validation function throws an exception, the default# behavior is changed to still force a success## The access_token and _refresh_token is supplied by the calling client to your refresh APIrefresh_tokens(application_name='your-great-app',access_token=access_token,refresh_token=refresh_token,request_id=request_id,user_validation_function=my_validation_function,default_user_validation_function_result_on_exception=True)ImplementationThis library can be used in two different contexts:During user authentication which will result in aaccess_tokenanduser_tokenbeing issuesDuring API requests, where a previously issuedaccess_tokenis validated and some tests are done before the request is authorized.For the authentication portion, you will probably implement this library at the authentication API end-point.For authorization, you would typically implement this library in your API Gateway or proxy server in the authorization leg.AuthenticationYou will still need to implement the actual method of authentication. This process is facilitated by theBackEndAuthenticatorclass which you need to extend with your own implementation of theauthenticatemethod.Theauthenticatemethod returns aAuthenticationResultwhich contains enough information to finally construct the dictionary that will hold theaccess tokenanduser token.The JSON Web Token (generated frompyjwt_wrapper.authentication)Thus far, this library only supportusernameandpasswordauthentication in a function calledauthenticate_using_user_credentials(name may change in future).Access TokenThe access token is typically used by one application that is requesting resources from another application to authorize the first application. The authorization is usually done by some kind of a proxy in front of the second application. A web page rendered in a user's web browser that requests some data from an application hosted on the Internet is a typical example of this setup. In this case the web page will include theaccess_tokenwith each request to the application API. Each request has to pass the authorization check before the application will respond with the requested data.There are a number of resources on the Internet that will explain in a lot more detail the mechanics of authorization, but Auth0 has a very goodlightweight explanationof how the process would work.Theaccess_tokencontains the following elements:iss- Theissuer claimwhich is the value of theapplication_nameparameter. In web applications you could derive this from theHostheader.sub- Thesubject claimis currently mapped to theusernamein username/passwords authenticationaud- Theaudience claimwhich is also mapped to theapplication_nameparameter at the moment.exp- Theexpiration time claimwhich is the Unix timestamp (UTC) after which this token is no longer considered valid.nbf- Thenot before claimwhich is set to the Unix timestamp (UTC) of token creationiat- Theissued at claimwhich is set to the Unix timestamp (UTC) of token creationjti- TheJWT ID claimwhich is mapped to therequest_idparameterprm- A list of permission names (strings) that was valid for the user at the time of authentication. This may be useful in some front-end application to decide which components to render. For example, only provide administrator controls/components to the users that have admin rights.extra- An optional parameter that will only be present if theBackEndAuthenticatorincluded extra data to include in the token. This is a standard dictionary and should only contain primitives.User TokenThe user token (also known as the ID token) contains basic user profile information.Theaccess_tokencontains the following elements:iss- Theissuer claimwhich is the value of theapplication_nameparameter. In web applications you could derive this from theHostheader.sub- Thesubject claimis currently mapped to theusernamein username/passwords authenticationcontext- This value is from theapplication_nameparameter.extra- An optional parameter that will only be present if theBackEndAuthenticatorincluded extra data to include in the token. This is a standard dictionary and should only contain primitives.Authorization (as implemented by frompyjwt_wrapper.authorization)During the authorization phase, PyJWT is used for the general token validation.In addition, theauthorize_tokentakes a number of other parameters used in the authorization process:application_name, which will be used to validate theaudclaimrequired_permissionwhich is a parameter you pass in based on the type of request. The function will test if this value is present in theprmclaim values.LoggingThe library utilizes the standardPython logging frameworkand by default will use a STDOUT log handler.Most functions takes aloggerparameter which implements theLoggerclass. If you create your ownhandler, just create it yourself and initializeLoggerwith your handler.Example:frompyjwt_wrapperimportLoggerimportloggingfh=logging.FileHandler('spam.log')formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')fh.setFormatter(formatter)my_logger=Logger(logging_handler=fh)# later usage...frompyjwt_wrapper.authenticationimportauthenticate_using_user_credentialsauth_result=authenticate_using_user_credentials(application_name='my_aoo',username='some_user@example.tld',password='the world strongest password',logger=my_logger,)# Use the logger in your own app:my_logger.info(message='This is a test',request_id='some-request-reference...')Testing from SourceBasic steps:Clone the repositoryCreate a Python Virtual EnvironmentInstall dependenciesRun the unit testsGet teh coverage reportsThe steps above can all be summarized in the following list of Unix commands (bash or zsh):# Clone the repositorygitclonehttps://github.com/nicc777/pyjwt-wrapper.gitcdpyjwt-wrapper# Create a Python Virtual Environmentpython-mvenvvenv

.venv/bin/activate# Install dependenciespipinstallpyjwtcoverage# Run the unit testscoveragerun--source./src-munittestdiscover# Get teh coverage reportscoveragereport-mTo DoCreate a customizable authorization classCreate authorization caching feature