<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pipdeptree on K-Life Hack | Systems Architecture &amp; DevOps</title><link>https://klifehack.com/en/tags/pipdeptree/</link><description>Recent content in Pipdeptree on K-Life Hack | Systems Architecture &amp; DevOps</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 07 Nov 2026 11:00:53 +0900</lastBuildDate><atom:link href="https://klifehack.com/en/tags/pipdeptree/index.xml" rel="self" type="application/rss+xml"/><item><title>Vulnerabilities CVE-2026-120459 and CVE-2026-85394 in python-jose and PyJWT Migration Guidelines for FastAPI Authentication Layer</title><link>https://klifehack.com/en/p/fastapi-jwt-python-jose-migration/</link><pubDate>Sat, 07 Nov 2026 11:00:53 +0900</pubDate><guid>https://klifehack.com/en/p/fastapi-jwt-python-jose-migration/</guid><description>&lt;p&gt;In authentication and authorization infrastructures for microservices and API gateways, the robustness of token verification processes defines the security boundary of the entire system. Especially in stateless JWT (JSON Web Token) verification, even a slight defect in the internal implementation behavior of a library or key interpretation logic directly leads to vulnerabilities such as authorization bypass or privilege escalation. In &lt;code&gt;python-jose&lt;/code&gt;, which has long been used in tutorials for major frameworks such as FastAPI, vulnerabilities that allow empty-string HMAC secret keys and public key algorithm confusion attacks have been reported. Under circumstances where maintenance of the upstream repository has virtually ceased, this article details the technical background and migration procedures required to securely maintain production authentication infrastructure.&lt;/p&gt;
&lt;h2 id="technical-background-and-mechanisms-of-the-vulnerabilities"&gt;Technical Background and Mechanisms of the Vulnerabilities
&lt;/h2&gt;&lt;p&gt;In all versions of &lt;code&gt;python-jose&lt;/code&gt; prior to and including version 3.5.0, the presence of the following two critical vulnerabilities has been confirmed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;CVE-2026-120459&lt;/b&gt;: Acceptance of zero-length (empty string) HMAC secret keys&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CVE-2026-85394&lt;/b&gt;: Algorithm confusion attacks via DER-encoded binary public key input&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="cve-2026-120459-verification-bypass-via-empty-string-hmac-keys"&gt;CVE-2026-120459: Verification Bypass via Empty-String HMAC Keys
&lt;/h3&gt;&lt;p&gt;Symmetric-key cryptographic algorithms (HS256, HS384, HS512) perform signature verification based on the premise that a secret key is known only to the server. However, in the HMAC implementation classes of &lt;code&gt;python-jose&lt;/code&gt; (&lt;code&gt;jose.backends.native.HMACKey&lt;/code&gt; and &lt;code&gt;jose.backends.cryptography_backend.CryptographyHMACKey&lt;/code&gt;), although type checking and string encoding of the input key are performed, validation to ensure the byte length is greater than zero is completely absent.&lt;/p&gt;
&lt;p&gt;Due to this defect, if a secret key evaluates to &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; (empty string) due to undefined environment variable fallbacks or similar causes, an attacker can construct an arbitrary claims payload signed with an empty string and have it accepted by the server as a valid signature. Even if algorithms are explicitly restricted, such as &lt;code&gt;algorithms=[&amp;quot;HS256&amp;quot;]&lt;/code&gt;, this vulnerability cannot be prevented because the length and entropy of the key itself are not validated.&lt;/p&gt;
&lt;h3 id="cve-2026-85394-algorithm-confusion-via-der-public-keys"&gt;CVE-2026-85394: Algorithm Confusion via DER Public Keys
&lt;/h3&gt;&lt;p&gt;In asymmetric cryptography such as RS256, signatures are generated with a private key and verified with a public key. The public key is information disclosed to clients and external entities. The &amp;ldquo;algorithm confusion attack&amp;rdquo; is a technique where an attacker obtains a known public key, rewrites the &lt;code&gt;alg&lt;/code&gt; header of the JWT to &lt;code&gt;HS256&lt;/code&gt;, and generates a signature using the binary data of the public key as the HMAC secret key.&lt;/p&gt;
&lt;p&gt;⚠️ As a mitigation for CVE-2024-33663 in the past, &lt;code&gt;python-jose&lt;/code&gt; introduced a guard to reject text-format PEM headers (&lt;code&gt;-----BEGIN ...&lt;/code&gt;) as HMAC keys. However, when a public key is passed in binary DER encoding (&lt;code&gt;bytes&lt;/code&gt;), it completely circumvents this regex-based check. Furthermore, &lt;code&gt;jwt.decode()&lt;/code&gt; in &lt;code&gt;python-jose&lt;/code&gt; defaults to &lt;code&gt;None&lt;/code&gt; when the &lt;code&gt;algorithms&lt;/code&gt; argument is omitted, skipping algorithm verification entirely. As a result, HMAC signatures created with the public key are validated as legitimate.&lt;/p&gt;
&lt;h2 id="blind-spots-in-dependency-scanning-detection"&gt;Blind Spots in Dependency Scanning Detection
&lt;/h2&gt;&lt;p&gt;In automated vulnerability scanners introduced in standard CI/CD pipelines (such as &lt;code&gt;pip-audit&lt;/code&gt; and Dependabot), metadata synchronization discrepancies have emerged regarding these two CVEs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[CVE-2026-85394]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - GHSA-3qf3-8w2g-rqmx (Reviewed)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Mapped to PyPI as PYSEC-2026-4298
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Detectable by standard scanners
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[CVE-2026-120459]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - GHSA-7jv8-hwmh-86qc (Unreviewed)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Not reflected in PyPI&amp;#39;s vulnerability feed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Limited to Git commit ranges only in Google OSV, not linked to package versions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Risk of being missed by tools such as pip-audit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If an automated audit tool detects only CVE-2026-85394 and remediation is deferred under the assumption that &amp;ldquo;DER-formatted public keys are not handled, so it is safe,&amp;rdquo; there is a risk of overlooking CVE-2026-120459 (empty string secret key risk), which is not registered in PyPI metadata.&lt;/p&gt;
&lt;h2 id="dependency-investigation-and-temporary-mitigations-within-repositories"&gt;Dependency Investigation and Temporary Mitigations within Repositories
&lt;/h2&gt;&lt;p&gt;Immediately inspect whether &lt;code&gt;python-jose&lt;/code&gt; is utilized across the production codebase and transitive dependencies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Search for direct imports in source code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grep -rnE &lt;span style="color:#e6db74"&gt;&amp;#34;(from|import) jose&amp;#34;&lt;/span&gt; --include&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*.py&amp;#34;&lt;/span&gt; .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Check dependency manifests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grep -n &lt;span style="color:#e6db74"&gt;&amp;#34;python-jose&amp;#34;&lt;/span&gt; requirements*.txt pyproject.toml poetry.lock uv.lock
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Check reverse dependency tree (uv or pipdeptree)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv tree --invert --package python-jose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipdeptree --reverse --packages python-jose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🛠️ In environments where the codebase cannot be rewritten immediately, introduce fail-fast validation at application startup to neutralize the preconditions that trigger the vulnerabilities.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pydantic &lt;span style="color:#f92672"&gt;import&lt;/span&gt; field_validator
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pydantic_settings &lt;span style="color:#f92672"&gt;import&lt;/span&gt; BaseSettings
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. Startup environment variable check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RAW_SECRET &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;SECRET_KEY&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; RAW_SECRET &lt;span style="color:#f92672"&gt;or&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; RAW_SECRET&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RuntimeError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;CRITICAL: The SECRET_KEY environment variable is unset or empty. Halting process.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. Guarantee entropy via Pydantic Settings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SecuritySettings&lt;/span&gt;(BaseSettings):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; secret_key: str
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@field_validator&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;secret_key&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;validate_key_length&lt;/span&gt;(cls, v: str) &lt;span style="color:#f92672"&gt;-&amp;amp;&lt;/span&gt;gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; v &lt;span style="color:#f92672"&gt;or&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; v&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip() &lt;span style="color:#f92672"&gt;or&lt;/span&gt; len(v) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;lt; &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ValueError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;SECRET_KEY must be at least 32 characters long to ensure sufficient entropy.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="migration-implementation-to-pyjwt"&gt;Migration Implementation to PyJWT
&lt;/h2&gt;&lt;p&gt;Perform the migration to &lt;code&gt;PyJWT&lt;/code&gt; (version 2.15.0 or higher), which has also been adopted as standard in the official FastAPI documentation (PR #11589 onwards). Because earlier versions of &lt;code&gt;PyJWT&lt;/code&gt; (such as 2.13.0 and 2.14.0) contained distinct DoS and key confusion vulnerabilities, always specify &lt;code&gt;&amp;amp;gt;=2.15.0&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Package replacement&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip uninstall -y python-jose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install &lt;span style="color:#e6db74"&gt;&amp;#34;pyjwt&amp;amp;gt;=2.15.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# If using uv&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv remove python-jose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv add &lt;span style="color:#e6db74"&gt;&amp;#34;pyjwt&amp;amp;gt;=2.15.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="api-signature-differences-and-refactoring"&gt;API Signature Differences and Refactoring
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;python-jose&lt;/code&gt; and &lt;code&gt;PyJWT&lt;/code&gt; differ in their argument specifications and exception class hierarchies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Pre-migration: python-jose (3.5.0)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; jose &lt;span style="color:#f92672"&gt;import&lt;/span&gt; JWTError, jwt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; fastapi &lt;span style="color:#f92672"&gt;import&lt;/span&gt; HTTPException, status
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;credentials_exception &lt;span style="color:#f92672"&gt;=&lt;/span&gt; HTTPException(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status_code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;status&lt;span style="color:#f92672"&gt;.&lt;/span&gt;HTTP_401_UNAUTHORIZED,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;認証情報を検証できませんでした&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; headers&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;WWW-Authenticate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; jwt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; token,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SECRET_KEY,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; algorithms&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;HS256&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;leeway&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; JWTError:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; credentials_exception
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rewrite this into the following complete &lt;code&gt;PyJWT&lt;/code&gt; implementation. &lt;code&gt;leeway&lt;/code&gt; becomes a first-class keyword argument, and the exception handling scope changes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# After migration: PyJWT &amp;amp;gt;= 2.15.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; jwt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; jwt.exceptions &lt;span style="color:#f92672"&gt;import&lt;/span&gt; InvalidTokenError, PyJWTError
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; fastapi &lt;span style="color:#f92672"&gt;import&lt;/span&gt; HTTPException, status
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;credentials_exception &lt;span style="color:#f92672"&gt;=&lt;/span&gt; HTTPException(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status_code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;status&lt;span style="color:#f92672"&gt;.&lt;/span&gt;HTTP_401_UNAUTHORIZED,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Could not validate credentials&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; headers&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;WWW-Authenticate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# The algorithms parameter is required&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; jwt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; token,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SECRET_KEY,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; algorithms&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;HS256&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; leeway&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; InvalidTokenError:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Catch ExpiredSignatureError, DecodeError, etc. together&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; credentials_exception
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; PyJWTError &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; err:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Log system-level errors such as InvalidKeyError (invalid secret key)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; HTTPException(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status_code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;status&lt;span style="color:#f92672"&gt;.&lt;/span&gt;HTTP_500_INTERNAL_SERVER_ERROR,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Fatal inconsistency in token validation configuration&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ) &lt;span style="color:#f92672"&gt;from&lt;/span&gt; err
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="troubleshooting"&gt;Troubleshooting
&lt;/h2&gt;&lt;h3 id="frequent-unexpected-401-unauthorized-errors-after-migration"&gt;Frequent Unexpected 401 Unauthorized Errors After Migration
&lt;/h3&gt;&lt;p&gt;💡 Because the &lt;code&gt;algorithms&lt;/code&gt; argument was optional (defaulting to &lt;code&gt;None&lt;/code&gt;) in &lt;code&gt;python-jose&lt;/code&gt;, code running without an algorithm specification may have been operational. In &lt;code&gt;PyJWT&lt;/code&gt;, specifying the &lt;code&gt;algorithms&lt;/code&gt; argument is practically mandatory, and omitting it raises a &lt;code&gt;DecodeError&lt;/code&gt;. Because &lt;code&gt;DecodeError&lt;/code&gt; inherits from &lt;code&gt;InvalidTokenError&lt;/code&gt;, it gets caught by the standard 401 handler described above, resulting in authentication failures being returned without generating logs. Verify that all calling &lt;code&gt;jwt.decode()&lt;/code&gt; invocations contain an explicit specification such as &lt;code&gt;algorithms=[&amp;quot;HS256&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="handling-500-internal-server-error-when-the-secret-key-is-empty"&gt;Handling 500 Internal Server Error When the Secret Key Is Empty
&lt;/h3&gt;&lt;p&gt;⚠️ When an empty-string HMAC key is provided, &lt;code&gt;PyJWT&lt;/code&gt; raises &lt;code&gt;InvalidKeyError&lt;/code&gt;. This exception inherits directly from &lt;code&gt;PyJWTError&lt;/code&gt; rather than being a subclass of &lt;code&gt;InvalidTokenError&lt;/code&gt;. Therefore, if only an &lt;code&gt;except InvalidTokenError:&lt;/code&gt; block is defined, the exception will not be caught and will surface as an unhandled 500 error inside FastAPI. Thorough environment variable validation at startup is necessary to suppress the occurrence of this exception at runtime.&lt;/p&gt;
&lt;h3 id="terminal-verification-logs-after-installing-dependent-packages"&gt;Terminal Verification Logs After Installing Dependent Packages
&lt;/h3&gt;&lt;p&gt;Verify that package updates and removal of unnecessary transitive dependencies have been executed successfully.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ pip show python-jose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WARNING: Package(s) not found: python-jose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ pip show pyjwt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Name: PyJWT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Version: 2.15.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Summary: JSON Web Token implementation in Python
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Home-page: https://github.com/jpadilla/pyjwt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Author: Jose Padilla
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;License: MIT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Location: /opt/venv/lib/python3.11/site-packages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requires: cryptography
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Required-by: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ python -c &amp;#34;import jwt; print(jwt.__version__)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2.15.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="operational-notes"&gt;Operational Notes
&lt;/h2&gt;&lt;p&gt;The repository owner of &lt;code&gt;python-jose&lt;/code&gt; has publicly stated that they have no intention of continuing maintenance, and unmerged pull requests (such as PR #422) remain abandoned. While systems implementing proper key hygiene (using random secret keys of 32 bytes or more and explicitly specifying algorithms) do not immediately expose themselves to external attacks, continuing to operate a security-critical library whose upstream patch supply has completely ceased constitutes operational debt. Promptly complete the migration to &lt;code&gt;PyJWT &amp;amp;gt;= 2.15.0&lt;/code&gt;, which is the standard across the FastAPI ecosystem, or transition to &lt;code&gt;joserfc&lt;/code&gt; or &lt;code&gt;Authlib&lt;/code&gt; depending on requirements.&lt;/p&gt;</description></item></channel></rss>