{"id":4636,"library":"mohawk","title":"Mohawk: Hawk HTTP Authorization","description":"Mohawk is an alternate Python implementation of the Hawk HTTP authorization scheme. Hawk allows two parties to securely communicate with each other using messages signed by a shared key. It is based on HTTP MAC access authentication (which was derived from parts of OAuth 1.0). The library's API was designed to be intuitive, less prone to security problems, and more Pythonic compared to other implementations. The current version is 1.1.0, with the last major release in late 2019, suggesting a stable, mature library.","status":"active","version":"1.1.0","language":"en","source_language":"en","source_url":"https://github.com/kumar303/mohawk","tags":["authorization","http","hawk","security","authentication"],"install":[{"cmd":"pip install mohawk","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Compatibility layer for Python 2 and 3, noted as a requirement in older documentation for Python 2.7+ or 3.4+. While often implicitly handled, it's a foundational dependency.","package":"six","optional":false}],"imports":[{"symbol":"Sender","correct":"from mohawk import Sender"},{"symbol":"Receiver","correct":"from mohawk import Receiver"}],"quickstart":{"code":"import os\nfrom mohawk import Sender, Receiver\nfrom mohawk.exc import HawkAuthenticateError\n\n# --- Shared Credentials (typically stored securely) ---\ncredentials = {\n    'id': os.environ.get('HAWK_ID', 'some-id'),\n    'key': os.environ.get('HAWK_KEY', 'a super secret key'),\n    'algorithm': 'sha256'\n}\n\nurl = 'http://example.com/resource'\nmethod = 'POST'\ncontent = b'this is some test content'\ncontent_type = 'text/plain'\n\n# --- Sender (Client-side) ---\ndef make_hawk_request(credentials, url, method, content, content_type):\n    sender = Sender(\n        credentials,\n        url,\n        method,\n        content=content,\n        content_type=content_type\n    )\n    \n    headers = {\n        'Authorization': sender.request_header,\n        'Content-Type': content_type\n    }\n    print(f\"\\nSender generated Authorization header: {sender.request_header}\")\n    # In a real application, you would send this via requests.post(url, headers=headers, data=content)\n    return headers, content\n\n# --- Receiver (Server-side) ---\ndef receive_hawk_request(credentials, request_headers, request_content, url, method):\n    try:\n        # The `lookup_credentials` and `seen_nonce` are application-specific callbacks\n        # For this example, we'll use simple in-memory functions.\n        def lookup_credentials(sender_id):\n            if sender_id == credentials['id']:\n                return credentials\n            return None\n\n        # In a real app, this would check a database/cache for replay attacks\n        processed_nonces = set()\n        def seen_nonce(sender_id, nonce, timestamp):\n            # A simple, insecure example. DO NOT USE IN PRODUCTION.\n            # Real implementation needs a persistent, shared, and atomic store.\n            key = f\"{sender_id}:{nonce}:{timestamp}\"\n            if key in processed_nonces:\n                return True\n            processed_nonces.add(key)\n            return False\n\n        receiver = Receiver(\n            lookup_credentials,          # Callback to retrieve credentials\n            request_headers['Authorization'], # Incoming Authorization header\n            url,                         # Request URL\n            method,                      # Request method\n            content=request_content,     # Request body\n            content_type=request_headers['Content-Type'], # Request Content-Type\n            seen_nonce=seen_nonce,       # Callback to check for replay attacks\n        )\n        print(\"\\nReceiver: Hawk authentication successful!\")\n        print(f\"Sender ID: {receiver.credentials['id']}\")\n        print(f\"Ext data: {receiver.ext}\")\n\n        # Optionally, the receiver can sign its response\n        response_content = b'response from server'\n        response_content_type = 'text/plain'\n        receiver.respond(\n            content=response_content,\n            content_type=response_content_type\n        )\n        print(f\"Receiver generated Server-Authorization header: {receiver.response_header}\")\n        return True\n    except HawkAuthenticateError as e:\n        print(f\"\\nReceiver: Hawk authentication failed: {e}\")\n        return False\n\n# --- Simulate a request-response cycle ---\nrequest_headers, request_body = make_hawk_request(credentials, url, method, content, content_type)\n\n# Simulate server receiving and processing the request\nsuccess = receive_hawk_request(credentials, request_headers, request_body, url, method)\n\nif success:\n    print(\"End-to-end Hawk flow demonstrated successfully.\")\nelse:\n    print(\"Hawk flow failed.\")\n","lang":"python","description":"This quickstart demonstrates a basic Hawk authentication flow using `mohawk.Sender` to generate an authenticated request and `mohawk.Receiver` to verify it. It simulates the HTTP request and response headers and body. Note that `lookup_credentials` and `seen_nonce` callbacks are simplified for demonstration; a production application would integrate these with a secure credential store and a persistent, atomic nonce-checking mechanism to prevent replay attacks."},"warnings":[{"fix":"Ensure that Hawk header values are correctly encoded and do not contain disallowed escape characters. Review the Hawk specification for valid header content.","message":"In version 1.0.0, escape characters (like backslash) in Hawk header values are no longer permitted. Clients relying on this behavior might break.","severity":"breaking","affected_versions":">=1.0.0"},{"fix":"Always provide `content` and `content_type` when there is content, or explicitly set `accept_untrusted_content=True` if content hashing is intentionally skipped. Handle `mohawk.exc.MissingContent` if relevant.","message":"As of version 1.0.0, failing to provide `content` and `content_type` arguments to `mohawk.Receiver` or `mohawk.Sender.accept_response()` without explicitly setting `accept_untrusted_content=True` will now raise `mohawk.exc.MissingContent` instead of `ValueError`.","severity":"breaking","affected_versions":">=1.0.0"},{"fix":"Update your `seen_nonce` callback function to accept `sender_id` as the first argument, e.g., `def seen_nonce(sender_id, nonce, timestamp):`.","message":"In version 0.3.0, the signature for the `seen_nonce()` callback changed from `(nonce, timestamp)` to `(sender_id, nonce, timestamp)`.","severity":"breaking","affected_versions":">=0.3.0"},{"fix":"Implement a `seen_nonce(sender_id, nonce, timestamp)` callable that checks a persistent, atomic store (e.g., database, Redis) to determine if a nonce for a given sender and timestamp has already been processed. Return `True` if seen, `False` otherwise.","message":"Mohawk does not provide a default implementation for checking nonces, which is critical for preventing replay attacks. Your application *must* implement and provide a `seen_nonce` callback.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure all servers involved in Hawk communication have their clocks synchronized using a reliable service (e.g., NTP, TLSdate). Hawk provides mechanisms for senders to adjust timestamps, but proper server clock sync is foundational.","message":"Accurate clock synchronization between sender and receiver servers is crucial. Timestamp discrepancies can lead to `mohawk.exc.TokenExpired` exceptions.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Only disable content hashing if you fully understand the security implications and have alternative integrity checks in place. For most use cases, content hashing should remain enabled to prevent tampering.","message":"By default, Mohawk enforces content hashing. If you explicitly skip content hashing (e.g., by setting `always_hash_content=False` or `accept_untrusted_content=True`), your application could be susceptible to content tampering if not handled with extreme care.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-12T00:00:00.000Z","next_check":"2026-07-11T00:00:00.000Z"}