From: Michael Tremer Date: Fri, 20 Jun 2025 09:54:26 +0000 (+0000) Subject: API: Set the refresh token in a cookie as well X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1bb92e1df0fb53aead9519bcb57b169062716fc3;p=pbs.git API: Set the refresh token in a cookie as well This will make it easier to refresh the token from a browser when it is persistently stored as a cookie. Signed-off-by: Michael Tremer --- diff --git a/src/api/auth.py b/src/api/auth.py index 0a198277..7cfce981 100644 --- a/src/api/auth.py +++ b/src/api/auth.py @@ -25,6 +25,7 @@ import kerberos import os import pydantic import socket +import typing from . import apiv1 from . import backend @@ -136,7 +137,7 @@ def get_principal(token): @router.post("/user") async def auth_user(credentials: fastapi.security.OAuth2PasswordRequestForm = - fastapi.Depends()) -> AuthResponse: + fastapi.Depends()) -> fastapi.responses.JSONResponse: # Set keytab to use os.environ["KRB5_KTNAME"] = KERBEROS_KEYTAB @@ -165,16 +166,39 @@ async def auth_user(credentials: fastapi.security.OAuth2PasswordRequestForm = type="refresh", expires_after=REFRESH_TOKEN_EXPIRY_TIME) # Send the response - return AuthResponse(access_token=access_token, refresh_token=refresh_token) + data = AuthResponse(access_token=access_token, refresh_token=refresh_token) + + # Serialize the JSON response + response = fastapi.responses.JSONResponse(content=data.model_dump()) + + # Set the refresh token as a cookie too for persistent browser storage + response.set_cookie( + "refresh_token", + refresh_token, + httponly=True, + samesite="Strict", + + # Only send the cookie when performing a refresh + path="/api/v1/auth/refresh", + # Forget the cookie when the token expires + max_age=REFRESH_TOKEN_EXPIRY_TIME, + ) + + return response class RefreshRequest(pydantic.BaseModel): refresh_token: str @router.post("/refresh") -async def auth_refresh(data: RefreshRequest): +async def auth_refresh(request: fastapi.Request, data: typing.Optional[RefreshRequest]=None): + # Fetch the refresh token from either the body or the cookie + refresh_token = data.refresh_token if data else request.cookies.get("refresh_token") + if not refresh_token: + raise fastapi.HTTPException(401, "No refresh token provided") + # Fetch the principal from the given token - principal = get_principal(data.refresh_token) + principal = get_principal(refresh_token) # XXX Check if the principal actually still exists @@ -183,7 +207,7 @@ async def auth_refresh(data: RefreshRequest): type="access", expires_after=ACCESS_TOKEN_EXPIRY_TIME) # Send the response - return AuthResponse(access_token=access_token, refresh_token=data.refresh_token) + return AuthResponse(access_token=access_token, refresh_token=refresh_token) # Add everything to the app apiv1.include_router(router)