Package: chowkidar-strawberry
ChowkidarA simple, flexible JWT authentication plugin for your Django Strawberry GraphQL APIs.InstallationInstall the package from PyPI:pipinstallchowkidar-strawberryAddchowkidarto yourINSTALLED_APPS:INSTALLED_APPS=[..."chowkidar",]Addchowkidar.extensions.JWTAuthExtensionto your strawberry schema extensions:-fromchowkidar.extensionimportJWTAuthExtensionschema=strawberry.Schema(query=Query,mutation=Mutation,extensions=[JWTAuthExtension],)Wrap your Strawberry GraphQL view withchowkidar.view.auth_enabled_viewin yoururls.py:fromchowkidar.viewimportauth_enabled_viewurlpatterns=[...path("graphql/",auth_enabled_view(GraphQLView.as_view(schema=schema,graphiql=settings.DEBUG))),]Create a Refresh Token Model in an app of your choice inheriting thechowkidar.models.AbstractRefreshTokenabstract model:classRefreshToken(AbstractRefreshToken,models.Model):pass(do not forget run topython manage.py makemigrationsandpython manage.py migrate)Add 'REFRESH_TOKEN_MODEL' to yoursettings.pyand point it to the Refresh Token Model you created in the previous step:REFRESH_TOKEN_MODEL='<your app>.RefreshToken'Implement Mutations forloginandlogoutwithissue_tokens_on_loginandrevoke_tokens_on_logoutrespectively:importstrawberryfromchowkidar.wrappersimportissue_tokens_on_login,revoke_tokens_on_logoutfromchowkidar.authenticationimportauthenticate# You may also use your own authentication methods or other methods like `authenticate_with_email` from chowkidar as well.@strawberry.typeclassAuthMutations:@strawberry.mutation@issue_tokens_on_logindeflogin(self,info,username:str,password:str)->bool:user=authenticate(username=username,password=password)ifuserisNone:raiseException("Invalid username or password")# Set LOGIN_USER with the authenticated user's object, in case of successful authentication# else, set LOGIN_USER to Noneinfo.context.LOGIN_USER=userreturnTrue@strawberry.mutation@revoke_tokens_on_logoutdeflogout(self,info)->bool:# Set info.context.LOGIN_USER to True for logging out the userinfo.context.LOGOUT_USER=TruereturnTrueAll your resolvers will now get the following parameters frominfo.context-info.context.userID- ID of the requesting user, None if not logged-ininfo.context.refreshToken- Refresh token string of the requesting user, None if not logged-inChowkidar comes with 3 authentication methods (importable fromchowkidar.authentication), which you may use -authenticate_with_email- authenticate with email and passwordauthenticate_with_username- authenticate with username and passwordauthenticate_with_oauth- authenticate with OAuth provider, usingauthorization_codemethodauthenticate- authenticate with username or email and passwordDecoratorsYou can use these decorators@login_required- wrap your resolver with this decorator to ensure the resolver is called only for logged-in users.fromchowkidar.decoratorsimportlogin_required@strawberry.typeclassQuery:@strawberry.field@login_requireddefmovies(self,info)->List[MovieType]:returnMovie.objects.all()@resolve_user- wrap this around your resolver to obtainUsermodel instance of the requesting user atinfo.context.user. Hits the Database. Will throw an exception if the user is not logged-in.importstrawberryfromchowkidar.decoratorsimportresolve_user@strawberry.typeclassMutation:@strawberry.mutation@resolve_userdefcreate_movie(self,name:str,info)->List[MovieType]:ifnotinfo.context.user.is_superuser:raiseException("Only superusers can create movies")# Note: Like you see here, for most queryset operations you can use - user_id=info.context.userID, without needing any decorator or hitting the DB.returnMovie.objects.create(name=name,user_id=info.context.userID)Integrating OAuth Login / Registrationimportstrawberryfromdjango.confimportsettingsfromchowkidar.wrappersimportissue_tokens_on_loginfromchowkidar.authentication.oauth.providersimportget_provider_configurationfromchowkidar.authentication.oauthimportOAuthClientConfigurationfromchowkidar.authenticationimportauthenticate_with_oauth@strawberry.typeclassMutation:@strawberry.mutation@issue_tokens_on_logindefloginWithOAuth(self,info,provider:str,code:str)->Optional[PersonalProfile]:ifprovider=="GITHUB":config={"clientID":settings.GITHUB_CLIENT_ID,"clientSecret":settings.GITHUB_CLIENT_SECRET,}elifprovider=="GOOGLE":config={"clientID":settings.GOOGLE_CLIENT_ID,"clientSecret":settings.GOOGLE_CLIENT_SECRET,}else:raiseAPIError("Invalid OAuth provider",code="INVALID_OAUTH_PROVIDER")clientConfig=OAuthClientConfiguration(clientID=config["clientID"],clientSecret=config["clientSecret"],redirectURI=f"{settings.DOMAIN}/oauth/{provider}",provider=get_provider_configuration(provider))try:response=authenticate_with_oauth(client=clientConfig,authorization_code=code,)exceptExceptionase:raiseAPIError(str(e),code="OAUTH_ERROR")ifresponse.user:user=response.userelse:# User does not exist, create a new useruser=User.objects.create(email=response.user_info["email"],username=response.user_info["username"],)info.context.LOGIN_USER=userreturnuser.personalProfileTracking IP Address & User Agent in Refresh TokenclassRefreshToken(AbstractRefreshToken,models.Model):ip=models.GenericIPAddressField(null=True,blank=True)userAgent=models.CharField(max_length=255,null=True,blank=True)defprocess_request_before_save(self,request:HttpRequest):# set IP from the requestfromipwareimportget_client_ipip,is_routable=get_client_ip(request)self.ip=ip# set user agent from the requestagent=Noneif"User-Agent"inrequest.headers:agent=request.headers["user-agent"]self.userAgent=agentSettingsHere are the available settings -REFRESH_TOKEN_MODEL = None # Required, a model that implements chowkidar.models.AbstractRefreshToken

JWT_REFRESH_TOKEN_N_BYTES: int = 20

# Expiry Settings

JWT_ACCESS_TOKEN_EXPIRATION_DELTA: timedelta = timezone.timedelta(seconds=60)
JWT_REFRESH_TOKEN_EXPIRATION_DELTA: timedelta = timezone.timedelta(seconds=60 * 60 * 24 * 7)

# Cookie Settings

JWT_ACCESS_TOKEN_COOKIE_NAME: str = 'JWT_ACCESS_TOKEN'
JWT_REFRESH_TOKEN_COOKIE_NAME: str = 'JWT_REFRESH_TOKEN'

JWT_COOKIE_DOMAIN: str = None
JWT_COOKIE_SAME_SITE: ['Lax', 'Strict', 'None'] = "Lax"
JWT_COOKIE_SECURE: bool = False
JWT_COOKIE_HTTP_ONLY: bool = True


# JWT Settings
JWT_SECRET_KEY: str = settings.SECRET_KEY
)
JWT_PUBLIC_KEY: str = None
JWT_PRIVATE_KEY: str = None

JWT_ALGORITHM: str = "HS256"
JWT_LEEWAY: int = 0
JWT_ISSUER: str = NoneHow it Works?Uses short-lived stateless JWT Access Token set as cookie to authenticate users. An additional, long-running stateful
JWT Refresh Token, that is recorded in RefreshToken model, is also generated to automatically to allow refreshing /
generating new access token when expired. This process is fully managed automatically at the backend. For issuing
new access token using a existing refresh token, the refresh token is validated against the DB. For all other requests,
the DB is not hit, but access key is simply validated against its key.settings.pyenlists various configuration options for this plugin. The default values are set to work out of the box
with minimal configuration. You can override these values in your project'ssettings.pyto customize the behavior.Uses acustom Strawberry Extensionto read JWT cookies from the
request, for validation, and auto issuing new access token using refresh token when available. Also sets upinfo.context.userIDfor easy access to the authenticated user's ID in resolvers. This extension is valid throughout
the resolving period of the GraphQL request, although auth is processed before actual query execution. This is defined
inextensions.py.Uses a wrapper function that wraps the GraphQLView to manage cookies. Data for the cookies is sent to this function
via setting custom attribute inrequestobject fromextensions.py. This function executes after GraphQL has been
fully processed and http response is ready. This is defined inview.py.Consumer applications can custom write login/logout mutations, by wrapping those with@issue_tokens_on_loginand@revoke_tokens_on_logoutdecorators. These are defined inwrappers.pyConsumer APIs can decorate auth requiring resolvers with@login_required(or@resolve_user), as well as get
get the ID of the requesting user frominfo.context.userID. The decorators are defined indecorators.py.Publishingpythonsetup.pysdisttwineuploaddist/*AcknowledgementThis project is inspired by django-graphql-jwt & django-graphql-social-auth by flavors.ContributionContributions are welcome. Please open an issue or a PR.LicenseThis project is licensed under theGNU General Public License V3.Made byTrabodawith ❤️ in India 🇮🇳.