Skip to main content

Chowkidar

A simple, flexible JWT authentication plugin for your Django Strawberry GraphQL APIs.

Installation

  1. Install the package from PyPI:
pip install chowkidar-strawberry
  1. Add chowkidar to your INSTALLED_APPS:
INSTALLED_APPS = [
    ...
    "chowkidar",
]
  1. Add chowkidar.extensions.JWTAuthExtension to your strawberry schema extensions:-
from chowkidar.extension import JWTAuthExtension

schema = strawberry.Schema(
    query=Query,
    mutation=Mutation,
    extensions=[JWTAuthExtension],
)
  1. Wrap your Strawberry GraphQL view with chowkidar.view.auth_enabled_view in your urls.py:
from chowkidar.view import auth_enabled_view

urlpatterns = [
  ...
  path(
      "graphql/",
      auth_enabled_view(
          GraphQLView.as_view(schema=schema, graphiql=settings.DEBUG)
      )
  ),
]
  1. Create a Refresh Token Model in an app of your choice inheriting the chowkidar.models.AbstractRefreshToken abstract model:
class RefreshToken(AbstractRefreshToken, models.Model):
    pass

(do not forget run to python manage.py makemigrations and python manage.py migrate)

  1. Add 'REFRESH_TOKEN_MODEL' to your settings.py and point it to the Refresh Token Model you created in the previous step:
REFRESH_TOKEN_MODEL = '<your app>.RefreshToken'
  1. Implement Mutations for login and logout with issue_tokens_on_login and revoke_tokens_on_logout respectively:
import strawberry
from chowkidar.wrappers import issue_tokens_on_login, revoke_tokens_on_logout
from chowkidar.authentication import authenticate  # You may also use your own authentication methods or other methods like `authenticate_with_email` from chowkidar as well.


@strawberry.type
class AuthMutations:

  @strawberry.mutation
  @issue_tokens_on_login
  def login(self, info, username: str, password: str) -> bool:
    user = authenticate(username=username, password=password)
    if user is None:
      raise Exception("Invalid username or password")

    # Set LOGIN_USER with the authenticated user's object, in case of successful authentication
    # else, set LOGIN_USER to None
    info.context.LOGIN_USER = user

    return True

  @strawberry.mutation
  @revoke_tokens_on_logout
  def logout(self, info) -> bool:
    # Set info.context.LOGIN_USER to True for logging out the user
    info.context.LOGOUT_USER = True

    return True

All your resolvers will now get the following parameters from info.context -

  • info.context.userID - ID of the requesting user, None if not logged-in
  • info.context.refreshToken- Refresh token string of the requesting user, None if not logged-in

Chowkidar comes with 3 authentication methods (importable from chowkidar.authentication), which you may use -

  1. authenticate_with_email - authenticate with email and password
  2. authenticate_with_username - authenticate with username and password
  3. authenticate - authenticate with username or email and password

Decorators

You can use these decorators

  1. @login_required - wrap your resolver with this decorator to ensure the resolver is called only for logged-in users.
from chowkidar.decorators import login_required

@strawberry.type
class Query:

    @strawberry.field
    @login_required
    def movies(self, info) -> List[MovieType]:
        return Movie.objects.all()
  1. @resolve_user - wrap this around your resolver to obtain User model instance of the requesting user at info.context.user. Hits the Database. Will throw an exception if the user is not logged-in.
from chowkidar.decorators import resolve_user

@strawberry.type
class Mutation:

    @strawberry.mutation
    @resolve_user
    def create_movie(self, name: str, info) -> List[MovieType]:
        if not info.context.user.is_superuser:
            raise Exception("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.
        return Movie.objects.create(name=name, user_id=info.context.userID)

Tracking IP Address & User Agent in Refresh Token

class RefreshToken(AbstractRefreshToken, models.Model):
  ip = models.GenericIPAddressField(null=True, blank=True)
  userAgent = models.CharField(max_length=255, null=True, blank=True)

  def process_request_before_save(self, request: HttpRequest):
      # set IP from the request
      from ipware import get_client_ip
      ip, is_routable = get_client_ip(request)
      self.ip = ip

      # set user agent from the request
      agent = None
      if "User-Agent" in request.headers:
          agent = request.headers["user-agent"]
      self.userAgent = agent

Settings

Here 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 = None

How 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.py enlists 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's settings.py to customize the behavior.
  • Uses a custom Strawberry Extension to read JWT cookies from the request, for validation, and auto issuing new access token using refresh token when available. Also sets up info.context.userID for 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 in extensions.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 in request object from extensions.py. This function executes after GraphQL has been fully processed and http response is ready. This is defined in view.py.
  • Consumer applications can custom write login/logout mutations, by wrapping those with @issue_tokens_on_login and @revoke_tokens_on_logout decorators. These are defined in wrappers.py
  • Consumer APIs can decorate auth requiring resolvers with @login_required (or @resolve_user), as well as get get the ID of the requesting user from info.context.userID. The decorators are defined in decorators.py.

Publishing

    python setup.py sdist
    twine upload dist/*

Acknowledgement

This project is inspired by django-graphql-jwt & django-graphql-social-auth by flavors.

Contribution

Contributions are welcome. Please open an issue or a PR.

License

This project is licensed under the GNU General Public License V3.

Release files for chowkidar-strawberry 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chowkidar-strawberry 0.4.0
File Size Uploaded
chowkidar_strawberry-0.4.0.tar.gz 45.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chowkidar-strawberry 0.4.0
File Interpreter ABI Platform
chowkidar_strawberry-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size:98.1 kB

Release files / chowkidar_strawberry-0.4.0.tar.gz

Download URL chowkidar_strawberry-0.4.0.tar.gz
Size 45.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cb055cbfed44c39e08c14aa4f6d8d52017b92ff40224ad4ed88c4a6535e45b70
BLAKE2b-256 checksum
How to use checksums
52939db88777f6e150cf46595027baff037e878226975bd51d6ec764403be637
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / chowkidar_strawberry-0.4.0-py3-none-any.whl

Download URL chowkidar_strawberry-0.4.0-py3-none-any.whl
Size 52.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f037007d8d61d86a5b89c40637db662219a96df2733d41fe810d909112bef8f0
BLAKE2b-256 checksum
How to use checksums
b222deab2a5e11af3e8c7c1a6f055e541e0a7af1e49f922802ed64c8c226942f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.2

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.9

1 release file

0.2.8

1 release file

0.2.7

1 release file

0.2.6

1 release file

0.2.5

1 release file

0.2.4

1 release file

0.2.3

1 release file

0.2.2

1 release file

0.2.1

1 release file

0.2.0

1 release file

0.1.7

1 release file

0.1.6

1 release file

0.1.5

1 release file

0.1.4

1 release file

0.1.3

1 release file

0.1.2

1 release file

0.1.1

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page