django-sesame

repository·main·Indexed 21 days ago

https://github.com/aaugustin/django-sesame

Provides frictionless, stateless, token-based authentication for Django projects, commonly used to implement 'Magic Links'. It supports expiring and single-use tokens, scoped tokens for restricted resource access, and integration via middleware, decorators, or a dedicated LoginView. Compatible with custom user models and Django versions 4.2, 5.0, 5.1, and 5.2.

Tokens
14.4K
Snippets
48
Records
71
Agent score
76%

What's inside django-sesame

  1. Use scoped tokens to restrict token usage

    main

    To prevent a token created for one purpose from being reused for another, you can assign a scope to tokens. A token is only valid if the same scope used during generation is provided during authentication.

    Key Rules:

    • The default scope is an empty string ("").
    • Tokens generated with the default scope are only valid in the default scope.
    • Tokens generated with a specific scope (e.g., "report:66") are not valid in the default scope.
    • Recommendation: Reserve the default scope exclusively for user login. Use dedicated scopes for all other use cases (e.g., accessing specific resources).
    • Warning: sesame.middleware.AuthenticationMiddleware does not support scopes; it only accepts tokens generated with the default scope.
    >>> # Generating a scoped token
    >>> from sesame.utils import get_token
    >>> get_token(user, scope="report:66")
    'jISWHmrXr4zg8FHVZZuxhpHs'
  2. How django-sesame tokens are designed

    main

    django-sesame tokens are constructed using the following components:

    1. Primary Key: The encoded primary key of the user.
    2. Timestamp (Optional): Encoded if SESAME_MAX_AGE is enabled.
    3. Revocation Key: Used for invalidating tokens. It is derived from:
      • The user's password (unless SESAME_INVALIDATE_ON_PASSWORD_CHANGE is False).
      • The user's email (if SESAME_INVALIDATE_ON_EMAIL_CHANGE is True).
      • The user's last login date (if SESAME_ONE_TIME is True).
    4. MAC (Message Authentication Code): A signature to prevent tampering.

    Token Formats

    • Tokens v2 (Recommended): A cleaner, faster design that produces shorter tokens. The signature uses the Blake2 algorithm in keyed mode. The signature length defaults to 10 bytes but can be adjusted via SESAME_SIGNATURE_SIZE (1-64 bytes).
    • Tokens v1: The original format using HMAC-SHA1 via Django's Signer or TimestampSigner.

    By default, SESAME_TOKENS is set to ["sesame.tokens_v2", "sesame.tokens_v1"], meaning the system generates v2 tokens but accepts both v2 and v1.

  3. Common use cases for django-sesame

    main

    django-sesame supports several stateless, token-based authentication patterns:

    • Login by email: Providing magic links to mobile users to avoid password entry (e.g., Slack style). Use a short SESAME_MAX_AGE (e.g., 10 minutes).
    • Authenticated links: Allowing access to specific resources (like an offline report) via an emailed link. Use a longer SESAME_MAX_AGE (e.g., a few days). Note: To prevent email forwarding from logging a user into the whole site, ensure these links only grant access to specific views rather than performing a full session login.
    • Sharing links: Creating links for guests to view content, potentially using phantom accounts or reusing the existing user's account.
    • WebSocket authentication: Passing a generated token from the Django server to a client to authenticate a WebSocket connection.
    • Non-critical private sites: Allowing users to access personalized content via bookmarked authenticated URLs without managing passwords.
  4. How to invalidate django-sesame tokens

    main

    Tokens can be invalidated through several mechanisms:

    • Expiration: Configure a finite lifetime using SESAME_MAX_AGE. Once the lifetime expires, the token is rejected.
    • Single-use (One-time) Tokens: Enable SESAME_ONE_TIME to invalidate a token immediately after its first successful use. This works by tying the token to the user's last login date; authenticating the token updates that date, rendering the token invalid. Note: Using short-lived tokens is generally recommended over single-use tokens due to the risk of accidental invalidation (e.g., if a user logs in via another method).
    • Password Change: By default, changing a user's password invalidates their tokens. You can disable this by setting SESAME_INVALIDATE_ON_PASSWORD_CHANGE = False.
    • Email Change: Set SESAME_INVALIDATE_ON_EMAIL_CHANGE = True to invalidate tokens when a user's email is updated.
    • User Inactivity: If a user's is_active attribute is set to False, all their tokens are rejected.
    • Global Invalidation: To invalidate all tokens for all users:
      • For v2 tokens: Change the SESAME_KEY setting.
      • For v1 tokens: Change the SESAME_SALT setting.
  5. Authenticate tokens via AuthenticationMiddleware

    main

    The sesame.middleware.AuthenticationMiddleware provides site-wide authentication. When a valid token is found in a URL, the user is logged in automatically, and the token is removed from the URL via an HTTP 302 Redirect. This allows users to access views protected by login_required or LoginRequiredMixin using a single click.

    Setup: Add the middleware to your MIDDLEWARE setting immediately after Django's built-in AuthenticationMiddleware.

    Note for Safari users: You must install the ua extra to ensure compatibility with Safari: pip install 'django-sesame[ua]'

    MIDDLEWARE = [
        ...,
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        "sesame.middleware.AuthenticationMiddleware",
        ...,
    ]
  6. Configure django-sesame authentication backend

    main

    To enable token-based authentication, add sesame.backends.ModelBackend to your AUTHENTICATION_BACKENDS setting in settings.py. You should typically keep the default django.contrib.auth.backends.ModelBackend as well.

    AUTHENTICATION_BACKENDS = [
        "django.contrib.auth.backends.ModelBackend",
        "sesame.backends.ModelBackend",
    ]
  7. Authenticate scoped tokens

    main

    To validate a token that was generated with a specific scope, you must provide that same scope when authenticating. You can do this using get_user, the authenticate decorator, or the LoginView.

    from sesame.utils import get_user
    
    # Manual check
    def share_report(request, report_id):
        user = get_user(request, scope=f"report:{report_id}")
        if user is None:
            raise PermissionDenied
        ...
    
    # Using the decorator
    from sesame.decorators import authenticate
    
    @authenticate(scope="report:{report_id}")
    def share_report(request, report_id):
        ...
  8. Implement a custom packer for non-standard primary keys

    main

    If your primary key is not an integer or UUID (e.g., a custom string), the default binary representation might be inefficient. You can implement a custom packer by subclassing sesame.packers.BasePacker and setting SESAME_PACKER to the dotted Python path of your class.

    Example of a packer for 24-character hexadecimal strings:

    from sesame.packers import BasePacker
    
    class Packer(BasePacker):
    
        @staticmethod
        def pack_pk(user_pk):
            assert len(user_pk) == 24
            return bytes.fromhex(user_pk)
    
        @staticmethod
        def unpack_pk(data):
            return data[:12].hex(), data[12:]
    
    # In settings.py:
    # SESAME_PACKER = 'path.to.your.module.Packer'
  9. Configure the Sesame authentication backend

    main

    django-sesame requires a compatible authentication backend to be added to your AUTHENTICATION_BACKENDS setting.

    It provides:

    • sesame.backends.ModelBackend: A standard backend implementation.
    • sesame.backends.SesameBackendMixin: A mixin that provides the authenticate method for custom backends.