TCatshoek / fastapi-nextauth-jwt

FastAPI Dependency to decode nextauth generated JWTs, for use in projects that mix nextjs/nextauth and fastapi.
MIT License
115 stars 8 forks source link

Error: "Key must be 256 bit for alg A256CBC" with next-auth@5.0.0-beta.13 #8

Closed bestickley closed 6 months ago

bestickley commented 6 months ago

Hi, first thanks so much for this library! Exactly what I needed.

I'm trying to use this library with next-auth@5 and running into some errors. Here is what I've got:

app = FastAPI()

JWT = NextAuthJWT(
    secret="123...",
    cookie_name="__Secure-authjs.session-token",
    csrf_prevention_enabled=False,
    info=b"Auth.js Generated Encryption Key (__Secure-authjs.session-token)",
)

@app.post("/chat")
def chat(jwt: Annotated[dict, Depends(JWT)], question: QuestionPayload):
  ...

But when I hit the /chat endpoint, I get the error below. The important part of the error message seems to be: Key must be 256 bit for alg A256CBC. I tried finding the length of the key by printing the length of it here but the value is 32 which checks out since 32 * 8 = 256. Any ideas why I'm getting this error?

Expand for Error /Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi_nextauth_jwt/fastapi_nextauth_jwt.py:76: RuntimeWarning: NEXTAUTH_URL not set warnings.warn("NEXTAUTH_URL not set", RuntimeWarning) INFO: Started server process [68495] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) len of self.key: 32 Key must be 256 bit for alg A256CBC INFO: 127.0.0.1:50048 - "POST /chat HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application Traceback (most recent call last): File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/jwe.py", line 178, in decrypt plain_text = _decrypt_and_auth(cek_bytes, enc, cipher_text, iv, aad, auth_tag) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/jwe.py", line 231, in _decrypt_and_auth encryption_key, mac_key, key_len = _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/jwe.py", line 252, in _get_encryption_key_mac_key_and_key_length_from_cek encryption_key = jwk.construct(encryption_key_bytes, encryption_alg) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/jwk.py", line 79, in construct return key_class(key_data, algorithm) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/backends/cryptography_backend.py", line 456, in __init__ raise JWKError(f"Key must be 256 bit for alg {algorithm}") jose.exceptions.JWKError: Key must be 256 bit for alg A256CBC During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi_nextauth_jwt/fastapi_nextauth_jwt.py", line 119, in __call__ decrypted_token_string = jwe.decrypt(encrypted_token, self.key) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/jose/jwe.py", line 182, in decrypt raise JWEError(e) jose.exceptions.JWEError: Key must be 256 bit for alg A256CBC During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 407, in run_asgi result = await app( # type: ignore[func-returns-value] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__ return await self.app(scope, receive, send) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__ await super().__call__(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__ await self.middleware_stack(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__ raise exc File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__ await self.app(scope, receive, _send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 65, in __call__ await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 756, in __call__ await self.middleware_stack(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 776, in app await route.handle(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 297, in handle await self.app(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 77, in app await wrap_app_handling_exceptions(app, request)(scope, receive, send) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 72, in app response = await func(request) ^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi/routing.py", line 269, in app solved_result = await solve_dependencies( ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 602, in solve_dependencies solved = await run_in_threadpool(call, **sub_values) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/starlette/concurrency.py", line 42, in run_in_threadpool return await anyio.to_thread.run_sync(func, *args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/anyio/to_thread.py", line 56, in run_sync return await get_async_backend().run_sync_in_worker_thread( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 2144, in run_sync_in_worker_thread return await future ^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 851, in run result = context.run(func, *args) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/stickb/Library/Caches/pypoetry/virtualenvs/non-package-mode-cN-H_gxH-py3.12/lib/python3.12/site-packages/fastapi_nextauth_jwt/fastapi_nextauth_jwt.py", line 123, in __call__ raise InvalidTokenError(status_code=401, message="Invalid JWT format") fastapi_nextauth_jwt.exceptions.InvalidTokenError

And this is the request I'm using:

curl 'http://0.0.0.0:8080/chat' \
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.9' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'cookie: __Host-authjs.csrf-token=949584998a9d1e544e583f952a4d1975154f467b0c409e4ae5a8f7b474e38573%7C17aabb54409fb3f2594bf6872e8d3ca6711ee7b4bd045cf3f9fbeae53127bc7a; __Secure-authjs.callback-url=https%3A%2F%2Flighthouse-dev.stickb.people.aws.dev%2Fcatalog; __Secure-authjs.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoiSFZ3TEtwNXVEcEdtbEVkQ19ubk5iLTBVeU84QnNvc3RQeGczd3JlRzd2THd3SjBwWGU4SnlQUWQwNXA1MkV3YV9uazBmLUVTVnA5VG01dTNnTDNoM2cifQ..zrA_o-gX4_YZn1tNSNv6fQ.jsvEbGZLRnvRHwQ0Gxp6sIz_yPbGcPzIu5SCNC82BHsn8b9a0Q_Ev9jp2WjCjjBCcHRCbGlVd0Q3vc7JpfADCjb1sW4Lk4tYniSntta_B_4Zp0BiXB5Ph2y_NZ7nrvMMDWaUIeGkHK-QcQMsFgm0NGrpf2NO9DXJQRopODskHftEAzHlDXwtZzVmvVwwc4thNteZG2saYHlmX2XGU8GSFIgjDvbkhZL8t1SpFStE-EEhBeHEVWHHXCjcx1wPzD5E5uEnLMLoxpz2vbPzTOblQp3h5ebgj70xu2OLb6NsxFxP7nsL-6RubDbwWWe5V7aGHQPOUkyS9RFfshbBVWMlX-p-5x8OGcH17x5gNRSIfaqzBkFuxkhz1sH5LcEU0HeCFzEIqoR_Sen5PM2b-wt085pC2k_EZ6KkfWbcAHHAOOVPIm1l9_FowdBa_CqtuqFHncglubFGjomOOPLsQRczaC_z97EqNMdDDNIEaVLSIqYlDdRcmu1P6j7OMPv7uBlbdUqvlawqcwiMwUd34FbeV_cO3lAG5MiYILia1DKmkfxjZ9a9iQ_71ueEUmxfUwlq9H-S7kvEDgOLFKKI-qhy2RcV5J1OELS2X2Ga86wsxANnQO45S6qak7qT1NDK6mgovO1TKwzPp4GNvX61z0VzPxPHQEv_kWPQP6XTx6QeuT8UkFKhdwGCsFZQn3qp2nosNWtsr0f6Z4lmVHAo5f3GF8HoNJoKle9FFOWxiuKR6mkF6vxXUUS28awO5jjMvdmk7xauBMrMDI5YKTy2vscMECae_YrV_1cqeBSf9oN0-2j3qccboTYOx0aJZR9A5MgS-LSaralztXNxsg_7YzWV8cOA_RbTQCGtxoIO8rFjeG2LD1S-InUvOnA-q-qI_BnQoBWY4GFmL21O68TxlCD_Vz8qV_BgWPgG0yI2o9Q3DhcuXNk-tJqa5fcHvBElzj3d9GfYIaSZRtqIpOIMQzkAXriWTY98XsBubirHXW65dX3EQit2Ose7LEZcpXanYHrABmIsP4NgL3D3PVdA8F0UAyy-YDZi1PJbpoLQa9xt6NWsCZh2PDmGNTGudBU8QH4ixh5i8tV7jQb6XLlroUS8Y78w6db7ufDhCJ2Swn5wsKHPNSipovVwPClixM39zpVGpGLaQYh0utuSCvhDNqr13_ni_nK8fAjwByzFeFdz6NG2-agjeF3Dwzr4V8s20YV7gYJOK1aqzUdmadhQYylSIKdQnzx50Lr7zKvBygMDhQYvcaX3aHoAoW6YdPzPCjfMyniOswWfkMSfA45w5PVCySp4jwChrRIKkQCAHb5uh42P8IRfukxb1yC1a6FxG9SmqRWzZhaTK-fDqNp0vuD6epamng8MmJIUvfItdExjb1yDaSluC3lC86vxGjHBHvOK-Fl87S2ahsyj1edT2oVF3ta_EbczANKYuhqy9QY_SdpfHc6rTRPwp0EGsMy3koGZ2F-cSc_LcAg502bjVvzT1S_rWuTMVoIv_SoSPxY6kzvmCeMB-ba4kVR76AzgpLCZdGmVAFfG1DvZd2gtylL5vPqpI5-_U06KJCaLaV5sdilPKYGCf6oaH9Kqv9sYvdQct83d2D-AgalDMvzc04zLpBsLLbLqBjNLej2fuVVPsNgg36AWaSffcw-thJiXTb_Q-_gY_b-LanwJRKgP3ulFnHFGNA0YJw5RlcLMUaqI8q8bY-vTpAXuf9DjAfYyZXo3Qy9RM_P49tvvPVgy_CdJUWBZ4vLEMPHlq-3f8Jwgw64.FKMiJq6nzclISbHgqwaO3qeHelRSysGumFyngGkdtHs' \
  -H 'origin: https://lighthouse-dev.stickb.people.aws.dev' \
  -H 'pragma: no-cache' \
  -H 'priority: u=1, i' \
  -H 'referer: https://lighthouse-dev.stickb.people.aws.dev/catalog' \
  -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: same-origin' \
  -H 'sec-fetch-site: same-origin' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \
  --data-raw '{"user_question":"Tell me about purchase requests"}'
Internal Server Error% 
bestickley commented 6 months ago

After seeing the below note from Authjs docs here:

Note Auth.js JWTs are meant to be used by the same app that issued them. If you need JWT authentication for your third-party API, you should rely on your Identity Provider instead.

I've decided to add my identity providers id token to the user session payload and send that to FastAPI to validate instead. Leaving this open in case others run into same issue and want to resolve.

TCatshoek commented 6 months ago

Hi @bestickley,

Thanks for reporting this! Looks like Auth.js changed their default encryption algorithm and key derivation in v5, which my code does not take into account. I'll see if I can fix it!

bestickley commented 6 months ago

Hey @TCatshoek, thanks for the response! Default encryption algo could have changed, but I think my issue was that I included Cognito's id token in the payload which isn't compliant with payload cryptographically (don't totally understand it). Removing the id_token from the payload I think will resolve my issue.

I want to be able to change the cookie name and csrf cookie name dynamically based on request, so I ended up implementing my own solution. I used some of the code from this library, so thank you!

TCatshoek commented 6 months ago

I added support for Auth.js v5 in #9

I'll do a release soon, if you have time it would be great if you could give it a try!

TCatshoek commented 6 months ago

Fixed in #9,

Feel free to reopen if you run into any more issues!

bestickley commented 6 months ago

Wow. @TCatshoek, that is the fastest turn around I've ever seen in open-source. πŸ‘πŸ‘πŸ‘ You should win maintainer of the year award. This works perfectly. THANK YOU!