Package: djognito
DjognitoA DRF Authentication module for verifying JWT Token issued by AWS Cognito.DjognitoProblem StatementMy usecaseSolution̦̦TL;DR versionA detailed viewHow to use this moduleCommonCase 1: You only want to authenticate using JWTCase 2: You want to authenticate using JWT and attach attributes/permissionsAppendixPre-requisitesJWTAWS CognitoProblem StatementI wasn't able to find a DRF Authentication Module which simply allows you toVerify a JWTAttach auserobject to request if verification is successfulAssumption:userobject only requiresusernameattribute to be instantiated. Other attributes can be later added usingattach_attributeshook provided.Every solution I came across does a lookup onUsermodel in database, thus defeating thestatelessnessof JWT.My usecaseI have engineered the frontend to send relevant tokens usingcookies. I just wanted toVerify theJWTstored in${ACCESS_TOKEN_KEY}cookie.Attach auserobject to myrequestif verification succeeds.Avoid any DB lookups.Attach permissions to user depending on some of the JWT fields(cognito:groupsin my case).Solution̦̦TL;DR versionCreated a DRF Authentication Module which simply looks at the cookies and verifies the user. If user verification succeeds,
auserobject is created and attached torequestobject. Rest of the code can userequest.userto access the created user.A detailed viewThe authentication module picks up the JWT stored in cookie named${ACCESS_TOKEN_KEY}, verifies it, and creates a user if the verification succeeds. It only usesusernameto instantiate theuserobject.Note:It is expected thatusernameis present in yourJWTas a claim. If you're using AWS Cognito, then your JWT will contain ausernameattribute.The following assignment operation happensfromdjango.contrib.auth.modelsimportUseruser=User(username=username)# Username is created using username claim present in your JWTThen, the createduserobject and therequestis passed toattach_attributehook, which attaches necessary permissions (or attributes) to your user object. Theuserobject is modified in-place. Finally, if everything succeeds, theuserobject is returned by theauthenticatemethod, which in-turn attaches it torequestobject. Now, you can userequest.userobject anywhere in your downstream application.Note that this module is designed to avoid DB lookups at all.How to use this moduleCommonKindly ensure that following environment variables are set:ACCESS_TOKEN_KEY=#  Points to the cookie key containing access token issued by AWS CognitoAWS_COGNITO_APP_CLIENT_ID=# Your Cognito App client IDAWS_COGNITO_REGION=# Region in which your Cognito Server existsAWS_COGNITO_USER_POOL_ID=# Your Cognito User pool IDCase 1: You only want to authenticate using JWTEnsure that environment variables described incommonsections are setAdddjognito.authentication.BaseCognitoAuthenticationtoDEFAULT_AUTHENTICATION_CLASSESinsettings.py. Finally, yourREST_FRAMEWORKdict should look likeREST_FRAMEWORK={....'DEFAULT_AUTHENTICATION_CLASSES':("djognito.authentication.BaseCognitoAuthentication",....),....}Case 2: You want to authenticate using JWT and attach attributes/permissionsEnsure that environment variables described incommonsections are setCreate a class inheritingBaseCognitoAuthenticationand override theattach_attributesmethod.Add that class in yourDEFAULT_AUTHENTICATION_CLASSESlist.The following example attaches an attribute calledgroupsto user object. The downstream code can userequest.user.groupsanywhere to access the groups to which user belongs.This example assumes that every user is part of at least one AWS Cognito group.fromdjognito.authenticationimportBaseCognitoAuthenticationfromdjognito.jwt_utilsimportverify_jwtfromrest_frameworkimportauthenticationfromrest_frameworkimportexceptionsimportlogginglogger=logging.getLogger(__name__)classCognitoAuthentication(BaseCognitoAuthentication):defattach_attributes(self,user,request):access_token=request.COOKIES.get('accessToken','')claims=verify_jwt(access_token)user.groups=claims['cognito:groups']iflen(claims['cognito:groups'])>1:logger.warning(f'User{claims["username"]}belongs to multiple group:{claims["cognito: groups"]}.')Assuming that the filename isauthentication.pyandPYTHONPATHis able to locate it, addauthentication.CognitoAuthenticationto yourDEFAULT_AUTHENTICATION_CLASSES. Finally, yourREST_FRAMEWORKdict should look likeREST_FRAMEWORK={....'DEFAULT_AUTHENTICATION_CLASSES':("authentication.CognitoAuthentication",....),....}You can read more about the flow of controlhereAppendixPre-requisitesJWTOne of the primary usecase of JWT is stateless authentication. It allows you to assert the authenticity of a given user without performing a database lookup. There are two major advantages of this:Performance boost: database lookup may significantly increase your latencySeparation of Resources: The whole application can be divided into two distinct set of resources:AuthServerandResourceServer. This has following benefits:One can have a separate team/workforce to ensure thatAuthserver meets standard compliances (HIPAA,FedRAMPetc).ResourcesServerandAuthServercan scale independently.AWS CognitoAWS Cognito can act as aAuthServerfor systems relying on Stateless Authentication. It usesJWTstandard for issuing tokens and takes care of user management (sign-up, sign-in, account verification, MFA, etc). You can read more about AWS Cognitohere