4

I have tried to implement OAuth2 Cookie based Authentication using FastAPI. On calling /auth/token endpoint, it perfectly sets a HttpOnly cookie as shown below:

@router.post("/auth/token", response_model=Token)
async def get_token(response: Response, form_data: OAuth2PasswordRequestForm = Depends()):
    user = await authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=Config.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.email_id}, expires_delta=access_token_expires
    )
    response.set_cookie(key="access_token", value=access_token, httponly=True)
    return {"access_token": access_token, "token_type": "bearer"}

Similarly, It should delete that cookie immediately after calling /logout endpoint as below:

@router.get("/logout")
async def logout(request: Request, response: Response, current_user: User = Depends(get_current_active_user)):
    # Also tried following two comment lines
    # response.set_cookie(key="access_token", value="", max_age=1)
    # response.delete_cookie("access_token", domain="localhost")
    response.delete_cookie("access_token")
    return templates.TemplateResponse("login.html", {"request": request, "title": "Login", "current_user": AnonymousUser()})

Problem: After calling /logout endpoint it should delete the cookie which it does but when I again click on /login it is able to retrieve the same cookie with same auth token i.e. browser send the same cookie along with the auth token in the request.

Here is the debug state of response after deleting cookies. It has deleted cookie from response object which is good:

debugger state response object

Here is the debug state of request when I attempt to login AFTER LOGOUT. It is still able to retrieve that cookie from browser:

debugger state request object

Any help regarding how to delete the cookies properly so that it could not be found again after logging out ?

AKA
  • 504
  • 1
  • 8
  • 17
  • Hi @AKA, As a best practice suggested in this answer (https://stackoverflow.com/a/20320610/9058468) you can set the cookie with an expire value – prietosanti Apr 14 '21 at 15:54
  • @Santiago Yes, but on logout it has to be deleted immediately right. After logout, ideally it must ask credentials to login again but here it just doesnt ask that because it is able to retrieve it from request object. – AKA Apr 14 '21 at 16:17
  • I think the problem lies when it redirects after logging out. This will also happen for any custom header when you redirect. Try to return an empty string instead and see if it works. Also, take a look at [this answer](https://stackoverflow.com/a/41218304/9058468) – prietosanti Apr 14 '21 at 20:47
  • @Santiago Finally resolved it by doing a little change in the response. Thanks for pointing me into that direction. – AKA Apr 15 '21 at 18:18
  • glad to help. You should accept your answer – prietosanti Apr 15 '21 at 19:39

2 Answers2

7

Problem is with the response /logout endpoint is returning.

As shown below, the line response.delete_cookies(key="access_token") is able to delete the cookie successfully debug state of response object

On creating TemplateResponse object while returning, it creates a new response copying the same cookies from the request and kind of shadow the change (delete cookies) that we have done. So swapping last two lines in the logout() function solves the problem:

@router.get("/logout")
async def logout(request: Request, response: Response, current_user: User = Depends(get_current_active_user)):
    # Also tried following two comment lines
    # response.set_cookie(key="access_token", value="", max_age=1)
    # response.delete_cookie("access_token", domain="localhost")
    response = templates.TemplateResponse("login.html", {"request": request, "title": "Login", "current_user": AnonymousUser()})
    response.delete_cookie("access_token")
    return response
AKA
  • 504
  • 1
  • 8
  • 17
0

This three line code will also work.

@router.post("/logout")
async def logout(response: Response,):
    response.delete_cookie("bearer")
    return {"status":"success"}
boyenec
  • 1,405
  • 5
  • 29