set the secure cookie flag when the request arrived over tls

The session cookie only got the Secure flag from auth.cookie_secure, which defaults to false even though TLS is on by default, so browsers would send it over plain HTTP. The cookie is now Secure whenever nginx reports an https scheme, and nginx passes that scheme to the /auth subrequest so refreshed cookies match. cookie_secure still forces the flag for a reverse proxy that terminates TLS in front of plain-HTTP Frigate.
This commit is contained in:
Josh Hawkins
2026-10-18 12:18:58 -05:00
parent 3dc382938c
commit a2e7e5c991
6 changed files with 58 additions and 7 deletions
@@ -19,6 +19,7 @@ location /auth {
# this cannot be spoofed
proxy_set_header X-Go2rtc-Stream-Proxy $go2rtc_stream_proxy;
proxy_set_header X-Server-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Content-Length "";
# Pass along auth related info
proxy_set_header Authorization $http_authorization;
@@ -117,7 +117,8 @@ auth:
# Optional: Cookie to store the JWT token for native auth (default: shown below)
cookie_name: frigate_token
# Optional: Set secure flag on cookie. (default: shown below)
# NOTE: This should be set to True if you are using TLS
# NOTE: Frigate sets the flag automatically when serving TLS. Set this to True
# when a reverse proxy terminates TLS in front of Frigate
cookie_secure: False
# Optional: Session length in seconds (default: shown below)
session_length: 86400 # 24 hours
+11 -4
View File
@@ -490,8 +490,15 @@ def refresh_user_session(request: Request, username: str) -> None:
request.app.user_sessions[username] = _user_session(user)
def is_secure_request(request: Request) -> bool:
# nginx sets X-Forwarded-Proto to its own scheme, replacing any client value
return (
request.app.frigate_config.auth.cookie_secure
or request.headers.get("x-forwarded-proto") == "https"
)
def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, max_age, secure):
# TODO: ideally this would set secure as well, but that requires TLS
# Starlette sets SameSite=Lax by default. The cookie is still sent to
# same-site iframes (e.g. Home Assistant on the same host or domain), but
# not to cross-site ones. CSRF is also mitigated by requiring a custom
@@ -844,7 +851,7 @@ def auth(request: Request):
JWT_COOKIE_NAME,
new_encoded_jwt,
JWT_SESSION_LENGTH,
request.app.frigate_config.auth.cookie_secure,
is_secure_request(request),
)
success_response.headers["remote-user"] = user
@@ -963,7 +970,7 @@ def login(request: Request, body: AppPostLoginBody):
JWT_COOKIE_NAME,
encoded_jwt,
JWT_SESSION_LENGTH,
request.app.frigate_config.auth.cookie_secure,
is_secure_request(request),
)
# Clear admin_first_time_login flag after successful admin login so the
# UI stops showing the first-time login documentation link.
@@ -1138,7 +1145,7 @@ async def update_password(
JWT_COOKIE_NAME,
encoded_jwt,
JWT_SESSION_LENGTH,
request.app.frigate_config.auth.cookie_secure,
is_secure_request(request),
)
return response
+1 -1
View File
@@ -27,7 +27,7 @@ class AuthConfig(FrigateBaseModel):
cookie_secure: bool = Field(
default=False,
title="Secure cookie flag",
description="Set the secure flag on the auth cookie; should be true when using TLS.",
description="Always set the secure flag on the auth cookie. Frigate sets it automatically when serving TLS; enable this when a reverse proxy terminates TLS in front of Frigate.",
)
session_length: int = Field(
default=86400,
@@ -229,3 +229,45 @@ class TestAuthJwtRole(BaseTestHttp):
self.assertEqual(resp.status_code, 202)
self.assertIn("frigate_token=", resp.headers["set-cookie"])
def _login_cookie(self, headers: dict | None = None) -> str:
with AuthTestClient(self._create_app()) as client:
resp = client.post(
"/login",
json={"user": "bob", "password": PASSWORD},
headers=headers or {},
)
self.assertEqual(resp.status_code, 200)
return resp.headers["set-cookie"].lower()
def test_login_cookie_is_secure_over_tls(self):
cookie = self._login_cookie({"x-forwarded-proto": "https"})
self.assertIn("; secure", cookie)
def test_login_cookie_is_not_secure_over_http(self):
cookie = self._login_cookie({"x-forwarded-proto": "http"})
self.assertNotIn("; secure", cookie)
def test_cookie_secure_config_forces_secure(self):
self.minimal_config["auth"]["cookie_secure"] = True
cookie = self._login_cookie()
self.assertIn("; secure", cookie)
def test_refreshed_cookie_is_secure_over_tls(self):
app = self._create_app()
token = jwt.encode(
{"alg": "HS256"},
{"sub": "bob", "exp": int(time.time()) + 60, "iat": int(time.time())},
app.jwt_token,
)
with AuthTestClient(app) as client:
client.cookies.set("frigate_token", token)
resp = client.get(
"/auth",
headers={"x-server-port": "8971", "x-forwarded-proto": "https"},
)
self.assertEqual(resp.status_code, 202)
self.assertIn("; secure", resp.headers["set-cookie"].lower())
+1 -1
View File
@@ -40,7 +40,7 @@
},
"cookie_secure": {
"label": "Secure cookie flag",
"description": "Set the secure flag on the auth cookie; should be true when using TLS."
"description": "Always set the secure flag on the auth cookie. Frigate sets it automatically when serving TLS; enable this when a reverse proxy terminates TLS in front of Frigate."
},
"session_length": {
"label": "Session length",