From 4f9112995ffcfcf53c7fdb59091aa5b07ebd70da Mon Sep 17 00:00:00 2001 From: Jason Vallery Date: Sat, 18 Apr 2026 20:57:08 -0600 Subject: [PATCH] fix(auth): wrap JWT secret in OctKey for joserfc 1.x compatibility joserfc 1.x requires symmetric-algorithm keys to be `OctKey` instances rather than raw strings. Since `requirements-wheels.txt` pins `joserfc == 1.2.*`, every call to `create_encoded_jwt()` currently raises `joserfc.errors.MissingKeyError` at `jwt.encode`, which nginx surfaces as `500 Internal Server Error` on `POST /api/login`. The symmetric-verify path in `is_logged_in_without_redirect` has the same problem on `jwt.decode`. Reproduction docker exec pip show joserfc # 1.2.2 curl -X POST -H 'Content-Type: application/json' \ -d '{"user":"","password":""}' \ http://:5000/api/login # -> 500 Internal Server Error # log shows: joserfc.errors.MissingKeyError: missing_key Minimal repro inside the container: python3 -c " from joserfc import jwt jwt.encode({'alg':'HS256'}, {'sub':'t','exp':0}, 'some-secret') " # ValueError: Invalid key python3 -c " from joserfc import jwt from joserfc.jwk import OctKey print(jwt.encode({'alg':'HS256'}, {'sub':'t','exp':0}, OctKey.import_key('some-secret'))[:40]) " # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9... The Home Assistant Frigate integration trips this path on every configuration attempt because it always does `POST /api/login` with the credentials supplied in the HA config flow, producing a confusing "failed to connect" symptom rather than an auth error. Fix Wrap the secret with `OctKey.import_key(...)` at the two sites that call into `joserfc.jwt`. The stored secret file format and everything else stays the same; `get_jwt_secret()` continues to return a string. AI disclosure AI-assisted. I reviewed and tested the change end-to-end against a live Frigate 0.17.1 container running joserfc 1.2.2: reproduced the 500 via Home Assistant, identified the stack (auth.py line 366 in 0.17.1 / 404 on dev, and the decode call on 680), verified `OctKey.import_key(str)` accepts the existing secret format, applied the patch in-container, restarted, and confirmed `POST /api/login` now returns a clean `401 {"message":"Login failed"}` for bad credentials and issues a valid JWT cookie for good credentials. No new dependencies; `OctKey` is part of `joserfc` which is already pinned. --- frigate/api/auth.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frigate/api/auth.py b/frigate/api/auth.py index d1c9688186..70446c8773 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -16,6 +16,7 @@ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi.responses import JSONResponse, RedirectResponse from joserfc import jwt +from joserfc.jwk import OctKey from peewee import DoesNotExist from slowapi import Limiter @@ -401,10 +402,14 @@ def validate_password_strength(password: str) -> tuple[bool, Optional[str]]: def create_encoded_jwt(user, role, expiration, secret): + # joserfc 1.x requires an OctKey for symmetric algorithms instead of a + # raw string; passing the string raises joserfc.errors.MissingKeyError + # (surfaces as a 500 on POST /api/login). See requirements pin + # `joserfc == 1.2.*`. return jwt.encode( {"alg": "HS256"}, {"sub": user, "role": role, "exp": expiration, "iat": int(time.time())}, - secret, + OctKey.import_key(secret), ) @@ -677,7 +682,7 @@ def auth(request: Request): return fail_response try: - token = jwt.decode(encoded_token, request.app.jwt_token) + token = jwt.decode(encoded_token, OctKey.import_key(request.app.jwt_token)) if "sub" not in token.claims: logger.debug("user not set in jwt token") return fail_response