MushroomMaula / fastapi_login

FastAPI-Login tries to provide similar functionality as Flask-Login does.
https://pypi.org/project/fastapi-login
MIT License
639 stars 58 forks source link

401 ERROR - Cookie has wrong format #62

Closed Residuum22 closed 2 years ago

Residuum22 commented 3 years ago

Hi!

I think I have found a bug in the login with cookies. I always got 401 error, so I started to dig down in the script. Finally I have found the problem and the "hotfix" solution. Well: The byte array somewhere will be converted into string so in the cookie the b' * ' identifiers are remamining in the cookie and the jwt parser can not parse the data. For example: Token (cookie):

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtbWFyayIsImV4cCI6MTYzMzUzMzU2OH0.NQzZOhaA0hyQBd8gLuLbs7zRaJfbgYLADwLJtPDEUWw'

If I cuted down the first two byte and the last byte the decode works like a charm. Cutted token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtbWFyayIsImV4cCI6MTYzMzUzMzU2OH0.NQzZOhaA0hyQBd8gLuLbs7zRaJfbgYLADwLJtPDEUWw

Have you experienced this error?

MushroomMaula commented 3 years ago

This error is occuring because the cookie is expected to be a string not a byte array or bytes. Can you share the code how you set the cookie?

Residuum22 commented 3 years ago

Here is the environment of the creation of the cookie:

@app.post("/login")
def login(request: Request, username: str = Form(...), password: str = Form(...)):
#def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
    # Select users from database
    #user = load_user(form_data.username)
    loginUser = select(members.c.username, members.c.password).where(
        members.c.username == username)
    result = engine.execute(loginUser)
    row = result.fetchone()
    logger.info("Post login user is: " + row.username)

    # Check exist of the username
    # if user is None:
    #     return templates.TemplateResponse("login.html", {"request": request, "error_status": "Didn't find any user with this username! Try again or register!"})

    # Check match of the password
    if row.password == password:

        access_token = manager.create_access_token(
            data={"sub":username}
        )
        logger.info("Token has been created!")
        response = RedirectResponse(
            url="/frontpage", status_code=status.HTTP_302_FOUND)

        manager.set_cookie(response, access_token)
        #logger.info("Cookie has been created!")

        # return {'access_token': access_token, 'token_type': 'bearer'}
        return response

    else:
        #raise InvalidCredentialsException
        return templates.TemplateResponse("login.html", {"request": request, "error_status": "Password isn't correct! Try again!"})

And here is the user loader:

@manager.user_loader()
def load_user(username: str):
    loginUser = select(members.c.username, members.c.password).where(
        members.c.username == username)
    result = engine.execute(loginUser)
    row = result.fetchone()
    if row:
        logger.info("Loaded user is: " + row.username)
        print("Loaded user is: " + row.username)
        return row.username
    else:
        logger.error("User not found")
        print("User not found")
        return None
MushroomMaula commented 3 years ago

Honestly I have no idea why your code is not working. What version of fastapi-login are you using? Have you set the use_cookie=True argument and are you using the manager as a dependency?

This is the code I used to try if cookies are working as expected: You can try for yourself if this works by opening 127.0.0.1:8000/login

from fastapi import FastAPI, Depends
from starlette.status import HTTP_302_FOUND
from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse
from fastapi_login import LoginManager

DB = {"test-user@example.org": {"email": "test-user@example.org", "password": "hunter2"}}
app = FastAPI()
manager = LoginManager(secret="your-secret-key", token_url="/login", use_cookie=True)

@manager.user_loader()
def get_user(email: str):
    return DB[email]

@app.get('/login')
def login():
    """
    For testing purposes this automatically creates a access token and the redirects to /cookies
    """
    access_token = manager.create_access_token(
        data={'sub': 'test-user@example.org'}
    )
    print(f"Access token is: {access_token}")

    response = RedirectResponse(url="/cookies", status_code=HTTP_302_FOUND)
    manager.set_cookie(response, access_token)
    print(f"Response headers are: {response.headers}")

    print(f"Redirecting to '/cookies'")
    return response

@app.get('/cookies')
async def list_cookies(request: Request, user=Depends(manager)):
    """
    Returns a json response containing all cookies set in the request
    and tries to load the user from the token, if this fails a InvalidCredentialsException is returned
    """
    print('Authentication was successfull.')
    print(f'Authorization cookie: {request.cookies.get(manager.cookie_name)}')
    return {
        'cookies': request.cookies,
        'auth': {
            'successful': True,  # Otherwise a exception is returned
            'user': user
        }
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app)
MushroomMaula commented 2 years ago

Did this help?

Residuum22 commented 2 years ago

Sorry for the late reply, but I had not too much time in the last few days. Your code output:

{"cookies":{"access-token":"b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXVzZXJAZXhhbXBsZS5vcmciLCJleHAiOjE2MzM5NzAwNTB9.Xg60C82RSd4UtubpdmCtD10HeLEfioJLCVYRwvzy1sY'"},"auth":{"successful":true,"user":{"email":"test-user@example.org","password":"hunter2"}}}

I suspect if I am the only one, who having this problem, that there is something wrong with the wsl. Interesting.

MushroomMaula commented 2 years ago

Interestingly the access-token in your case still contains the b' * ', no matter how (i.e. browser, python requests or httpie) I cannot replicate that behaviour. Do you mind sharing which browser you use or how you get the response, just in case other people have the same issue?

Residuum22 commented 2 years ago

I am using Edge chromium lastest build, but this issue also exist in Firefox. I will try it on my native linux machine tomorrow and I will reply back!

MushroomMaula commented 2 years ago

Thank you. In just tried it inside a arch linux vm and it seemed to work for me just fine. Can you also share the output of pip list from inside your virtualenv?