{"id":8844,"library":"atlassian-jwt-auth","title":"Atlassian JWT Auth","description":"Atlassian JWT Auth provides a Python implementation of the Atlassian Service to Service Authentication specification, wrapping the PyJWT library. It enables applications to securely sign and verify JSON Web Tokens (JWTs) for communication with Atlassian products using both symmetric and asymmetric key pairs. The current version is 22.0.0, with major releases typically occurring annually or bi-annually.","status":"active","version":"22.0.0","language":"en","source_language":"en","source_url":"https://github.com/atlassian/asap-authentication-python","tags":["atlassian","jwt","auth","authentication","security","connect"],"install":[{"cmd":"pip install atlassian-jwt-auth","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core library for JWT encoding and decoding. Requires `[crypto]` extra for advanced algorithms.","package":"pyjwt","optional":false},{"reason":"Provides cryptographic primitives for JWT signing and verification, used by PyJWT.","package":"cryptography","optional":false},{"reason":"Used internally for HTTP requests, e.g., fetching public keys for verification.","package":"requests","optional":false}],"imports":[{"symbol":"AsymmetricSigningRequestAuthentication","correct":"from atlassian_jwt_auth import AsymmetricSigningRequestAuthentication"},{"symbol":"SigningRequestAuthentication","correct":"from atlassian_jwt_auth import SigningRequestAuthentication"},{"note":"The 'algorithms' module is part of PyJWT, not directly exposed by atlassian_jwt_auth.","wrong":"from atlassian_jwt_auth import algorithms","symbol":"algorithms","correct":"from jwt import algorithms"}],"quickstart":{"code":"import os\nimport time\nimport requests\nfrom atlassian_jwt_auth import AsymmetricSigningRequestAuthentication\n\n# --- Configuration (replace with your actual values) ---\n# Your Atlassian product's client key (usually from a descriptor or Atlassian Connect setup)\nATLASSIAN_CLIENT_KEY = os.environ.get('ATLASSIAN_CLIENT_KEY', 'your-client-key')\n# Your Add-on's base URL (e.g., 'https://your-app.atlassian.net')\nADDON_BASE_URL = os.environ.get('ADDON_BASE_URL', 'https://your-addon.example.com')\n# Path to your private key file (PEM format)\nPRIVATE_KEY_PATH = os.environ.get('PRIVATE_KEY_PATH', 'path/to/your/private_key.pem')\n# Your 'kid' (Key ID) for the private key\nKEY_ID = os.environ.get('KEY_ID', 'your-key-id')\n# Atlassian instance base URL you are communicating with (e.g., 'https://your-instance.atlassian.net')\nATLASSIAN_BASE_URL = os.environ.get('ATLASSIAN_BASE_URL', 'https://your-atlassian-instance.net')\n\n# Ensure dummy values are not used in production\nif 'your-' in ATLASSIAN_CLIENT_KEY or 'your-addon' in ADDON_BASE_URL or 'path/to/your/' in PRIVATE_KEY_PATH:\n    print(\"WARNING: Using dummy configuration values. Please set actual environment variables or hardcoded values.\")\n    # For a runnable example, let's create a dummy key file if it doesn't exist\n    if not os.path.exists(PRIVATE_KEY_PATH):\n        try:\n            from cryptography.hazmat.primitives.asymmetric import rsa\n            from cryptography.hazmat.primitives import serialization\n            private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)\n            with open(PRIVATE_KEY_PATH, 'wb') as f:\n                f.write(private_key.private_bytes(\n                    encoding=serialization.Encoding.PEM,\n                    format=serialization.PrivateFormat.PKCS8,\n                    encryption_algorithm=serialization.NoEncryption()\n                ))\n            print(f\"Dummy private key generated at {PRIVATE_KEY_PATH}\")\n        except ImportError:\n            print(\"Cannot generate dummy key: cryptography not fully installed or missing. Please provide a real key.\")\n            exit(1)\n\n\ntry:\n    with open(PRIVATE_KEY_PATH, 'rb') as key_file:\n        private_key_bytes = key_file.read()\n\n    # 1. Initialize the authentication provider\n    auth_provider = AsymmetricSigningRequestAuthentication(\n        client_key=ATLASSIAN_CLIENT_KEY,\n        key_id=KEY_ID,\n        private_key_pem=private_key_bytes,\n        base_url=ADDON_BASE_URL\n    )\n\n    # 2. Define the HTTP method and request URL\n    method = 'GET'\n    target_url_path = '/rest/api/latest/myself'\n    canonical_path = ADDON_BASE_URL + target_url_path\n    \n    # 3. Create a JWT token for the request\n    # 'uri' is the canonical path of the request being made to the Atlassian host\n    # 'qsh' (Query String Hash) is typically generated by Atlassian Connect frameworks.\n    # For simple cases without query params, you might pass an empty string or rely on framework behavior.\n    # This example assumes a basic GET request with no query parameters.\n    # In a real app, 'qsh' is crucial and typically provided by the Atlassian Connect lifecycle.\n    # For a GET request with no query params, qsh is calculated on canonical path without query. \n    # For this example, we'll use a placeholder 'qsh'. Real applications should calculate this correctly.\n    # You might need to use `atlassian_jwt_auth.url_utils.create_canonical_query_string()`\n    # and `atlassian_jwt_auth.url_utils.create_query_string_hash()` to generate a proper qsh.\n    # For this quickstart, we'll demonstrate the signing process assuming a qsh can be generated/provided.\n    \n    # A simple placeholder for qsh for demonstration. In real Atlassian Connect apps, this is vital.\n    qsh_value = 'some-pre-calculated-qsh' # REPLACE WITH ACTUAL QSH CALCULATION IF QUERY PARAMS EXIST\n\n    jwt_token = auth_provider.create_asymmetric_jwt(\n        method=method,\n        uri=target_url_path,\n        qsh=qsh_value,\n        token_lifetime_seconds=300 # Token valid for 5 minutes\n    )\n\n    # 4. Make an authenticated request using the JWT in the Authorization header\n    headers = {\n        'Authorization': f'JWT {jwt_token}',\n        'Accept': 'application/json'\n    }\n\n    print(f\"Generated JWT: {jwt_token}\")\n    print(f\"Making request to {ATLASSIAN_BASE_URL}{target_url_path}\")\n\n    # This part requires a real Atlassian instance to verify\n    # response = requests.get(f'{ATLASSIAN_BASE_URL}{target_url_path}', headers=headers)\n\n    # print(f\"Response status: {response.status_code}\")\n    # print(f\"Response body: {response.json()}\")\n\n    print(\"Request demonstration complete. Uncomment the 'requests.get' line to make a real call.\")\n    print(\"Remember to replace placeholder configuration and qsh calculation with real values.\")\n\nexcept FileNotFoundError:\n    print(f\"Error: Private key file not found at {PRIVATE_KEY_PATH}. Please ensure it exists and is accessible.\")\nexcept Exception as e:\n    print(f\"An unexpected error occurred: {e}\")\n","lang":"python","description":"This quickstart demonstrates how to use `AsymmetricSigningRequestAuthentication` to create a JWT for authenticating requests to an Atlassian instance. It initializes the authentication provider with your client key, key ID, private key, and add-on base URL, then generates a JWT to be included in the `Authorization` header of an HTTP request. This example includes a placeholder `qsh` (Query String Hash) which is critical in real Atlassian Connect applications and must be correctly calculated based on the target URL and query parameters. For demonstration purposes, it also includes a mechanism to generate a dummy private key if one doesn't exist, though a real key is required for actual authentication."},"warnings":[{"fix":"Upgrade to Python 3 (3.6+ recommended).","message":"Python 2 support was dropped in version 18.0.0. Projects still on Python 2 must either remain on an older version of `atlassian-jwt-auth` or migrate to Python 3.","severity":"breaking","affected_versions":">=18.0.0"},{"fix":"Remove the `signature_algorithm` argument from `SigningRequestAuthentication` instantiations. Ensure your key material is correctly formatted so the algorithm can be inferred.","message":"The constructor for `SigningRequestAuthentication` removed the `signature_algorithm` parameter in version 15.0.0. The algorithm is now inferred from the key material.","severity":"breaking","affected_versions":">=15.0.0"},{"fix":"Update calls from `create_asap_jwt(...)` to `create_oauth_2_bearer_token(...)`.","message":"The function `create_asap_jwt` was renamed to `create_oauth_2_bearer_token` in version 16.0.0 for better clarity regarding its specific use case (OAuth 2.0 bearer tokens).","severity":"breaking","affected_versions":">=16.0.0"},{"fix":"Ensure your project's `pyjwt` and `cryptography` dependencies are up-to-date and compatible with `atlassian-jwt-auth` by running `pip install --upgrade atlassian-jwt-auth 'pyjwt[crypto]' cryptography`.","message":"Version 22.0.0 (and newer) requires `PyJWT>=2.0.0` and `cryptography>=3.3.1`. Using older versions of these dependencies, especially `PyJWT<2.0.0`, will lead to import errors or runtime issues due to API changes in PyJWT.","severity":"gotcha","affected_versions":">=22.0.0"},{"fix":"Ensure `qsh` is calculated precisely according to Atlassian's canonical URL specification. This often involves using helper functions like `atlassian_jwt_auth.url_utils.create_canonical_query_string` and `create_query_string_hash`.","message":"Incorrect `qsh` (Query String Hash) calculation for requests to Atlassian products will result in 'Invalid JWT signature' or 'Authentication Failed' errors, even if the token itself is well-formed. The `qsh` is critical for Atlassian Connect authentication.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Synchronize your server's clock using NTP. Use a reasonable `token_lifetime_seconds` (e.g., 300 seconds) and ensure `iat` (issued at) and `exp` (expiration) claims are correctly generated relative to a synced clock. PyJWT generally handles `nbf` (not before) and `exp` with a configurable leeway.","message":"Time synchronization issues (clock skew) between your application and the Atlassian instance can cause JWTs to be rejected with 'token expired' errors, even if they appear valid.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Double-check your private key (for signing) and public key (for verification). Ensure the `qsh` parameter matches the canonicalized URL and query parameters of the request being made to the Atlassian host. Also, verify that the `alg` (algorithm) header in the JWT matches the expected algorithm.","cause":"This typically indicates an incorrect public/private key pair, a mismatched signature algorithm, or an improperly calculated `qsh` (Query String Hash).","error":"jwt.exceptions.InvalidSignatureError: Signature verification failed"},{"fix":"Synchronize server clocks using NTP. Increase the `token_lifetime_seconds` if necessary (e.g., to 300 seconds for a 5-minute validity). Ensure your system clock is accurate.","cause":"The `exp` (expiration time) claim in the JWT is in the past, often due to clock skew between the signing server and the verifying server, or too short a `token_lifetime_seconds`.","error":"jwt.exceptions.ExpiredSignatureError: Signature has expired"},{"fix":"Remove the `signature_algorithm` argument from the constructor. The algorithm is now inferred automatically from the provided private key.","cause":"Attempting to pass `signature_algorithm` to `AsymmetricSigningRequestAuthentication` in a version where it's no longer accepted (since v15.0.0).","error":"TypeError: AsymmetricSigningRequestAuthentication.__init__() got an unexpected keyword argument 'signature_algorithm'"},{"fix":"Ensure you are running `PyJWT>=2.0.0`. If you need to access algorithms directly, import them from `jwt.algorithms` or `jwt` depending on the specific PyJWT version, not `atlassian_jwt_auth`.","cause":"This error occurs if you are using an older version of PyJWT (e.g., `PyJWT<2.0.0`) where the `algorithms` module might not have been directly exposed or structured differently, or if you're trying to import it from `atlassian_jwt_auth` instead of `jwt`.","error":"ImportError: cannot import name 'algorithms' from 'jwt'"}]}