Code Monkey home page Code Monkey logo

fastapi_login's People

Contributors

alwye avatar cdpath avatar dependabot[bot] avatar falkben avatar filwaline avatar kazunorimiura avatar kigawas avatar lexachoc avatar madi-s avatar marciomazza avatar mushroommaula avatar rafelpermata avatar zarlo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastapi_login's Issues

Middleware seems less reliable than Depends

Hi,

Thanks for the fastapi_login package.

I have noticed what I think is some odd behavior. I set up the middleware as suggested to populate the request.state.user object. But I noticed that I was not getting the user information even though there was a valid cookie with the token...

So I switched to using user=Depends(auth_manager) on my endpoints that require a login and I have not seen a problem since...

Is this a known problem? Is there anything that I could have messed up in my code that would cause this kind of behavior? Is there some logging debugging I can turn on to provide additional data?

Exception handling

When I use the example code from https://fastapi-login.readthedocs.io/advanced_usage/ and there is no, or an invalid token present in the request I get an error:

INFO:     127.0.0.1:5898 - "GET /proteced HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\base.py", line 41, in call_next
    message = await recv_stream.receive()
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\anyio\streams\memory.py", line 81, in receive
    return self.receive_nowait()
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\anyio\streams\memory.py", line 74, in receive_nowait
    raise EndOfStream
anyio.EndOfStream

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 373, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi\applications.py", line 261, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
    raise exc
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\base.py", line 63, in __call__
    response = await self.dispatch_func(request, call_next)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi_login\fastapi_login.py", line 439, in user_middleware
    return await call_next(request)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\base.py", line 44, in call_next
    raise app_exc
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\middleware\base.py", line 34, in coro
    await self.app(scope, request.receive, send_stream.send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    raise exc
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 21, in __call__
    raise e
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\starlette\routing.py", line 61, in app
    response = await func(request)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi\routing.py", line 217, in app
    solved_result = await solve_dependencies(
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi\dependencies\utils.py", line 527, in solve_dependencies
    solved = await call(**sub_values)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi_login\fastapi_login.py", line 405, in __call__
    token = await self._get_token(request)
  File "C:\Users\arne\AppData\Local\Programs\Python\Python39\lib\site-packages\fastapi_login\fastapi_login.py", line 349, in _get_token
    except type(self.not_authenticated_exception):
TypeError: catching classes that do not inherit from BaseException is not allowed

I use:

class NotAuthenticatedException(Exception):
    pass

secret = SECRET
manager = LoginManager(SECRET, '/login', use_header=False, use_cookie=True, custom_exception=NotAuthenticatedException)
@app.exception_handler(NotAuthenticatedException)
def auth_exception_handler(request: Request, exc: NotAuthenticatedException):
    """
    Redirect the user to the login page if not logged in
    """
    return RedirectResponse(url='/login')

What do I do wrong?

Cookie for request.state.user

When you create an access token and set it as a cookie - can you somehow call the user like how it is in the documentation?

    user = request.state.user
    if user is None:
        raise HTTPException(401)

or is that simply not possible when utilizing cookies?

Even more clear example with frontend also

Can you give a complete example , like the frontend and the corresponding backend code as well . Like a where route "/" will give a html login page and based on the username an password it goes to successful page and it's corresponding backend fastapi-login code.
Asking this because i am new to fastapi
Thanks in advance !!!

error checking inheritance of fastapi.params.Depends object

I keep getting this error when trying the example for the /protected endpoint

File "pydantic/validators.py", line 569, in find_validators
RuntimeError: error checking inheritance of <fastapi.params.Depends object at 0x7f6e17ddb940> (type: Depends)

Logout route

Hi! I am new to fastapi and came across this useful library, which now I am using to implement user login and authentication.
I also want to create a route for user logout, and wanted to know how to do that? Would that require doing something with LoginManager, as it is responsible for generating access_token?

about async

very cool but how to build an async user_loader?

Redirects issues on NotAuthenticatedException

Hi, I'm having issues with the redirects on NotAuthenticatedException. Here's how I have things currently setup:

class NotAuthenticatedException(Exception):
    pass

# these two argument are mandatory
def exc_handler(request, exc):
    return RedirectResponse(url='/login')

manager = LoginManager(SECRET, tokenUrl='/auth/token', use_cookie=True)
manager.cookie_name = 'oversight_token'
manager.not_authenticated_exception = NotAuthenticatedException
app.add_exception_handler(NotAuthenticatedException, exc_handler)

This works for me but only on routes with GET methods. On routes with POST I get the result below on the browser but no redirects. The problem is, I can only get the login redirects with POST routes as stated here.

{"detail":"Could not validate credentials"}

AttributeError: 'dict' object has no attribute 'set_cookie'

`@app.post('/login')
def login(data: UserCreate, response: Response, db=Depends(get_db)):
email = data.email
password = data.password

user = get_user(email)  # we are using the same function to retrieve the user
if user is None:
    raise InvalidCredentialsException  # you can also use your own HTTPException
elif not verify_password(password, user.password):
    raise InvalidCredentialsException

access_token = manager.create_access_token(
    data=dict(sub=user.email)
)
response = {
    'access_token': access_token,
    'user': UserResponse(**user.__dict__),
    'token_type': 'Bearer'
}
manager.set_cookie(response, access_token)
return response`

Enforcing authorization

Hello, how would I go about enforcing authorization? For example specific roles and permissions. I currently do this by not showing menus but that still leaves the endpoints open if the user knows the URL to enter to access a given page. I would like to enforce this when the request is be sent just like Authentication but this time for specific Authorization. I am not sure where is the best to handle this? I was thinking in the user_loader function and raise an exception if the user is not authorized even though authenticated for a given route/request.

Thanks in advanced!

Cookie expiration

Hi, I currently have things setup where I use cookies with fastapi_login.

manager = LoginManager(SECRET, tokenUrl='/auth/token', use_cookie=True)
manager.cookie_name = 'oversight_token'

On the auth route I build a response object with a RedirectResponse and set the cookie there.

access_token = manager.create_access_token(
    data=dict(sub=email)
)

response = RedirectResponse(url='/bids')
response.set_cookie(
    key=manager.cookie_name,
    value=f"Bearer {access_token}",
    httponly=True,
    max_age=2147483647,
    expires=2147483647,
)
return response

I have tried setting the max_age and expires values for the cookie to the maximum I could but after a while the site requires me to sign again. So my question is, how can I extend this time to the maximum? I'm behind a couple of firewalls and and internal VPN so security after login is not an issue for me. I'm have a couple of other minor issues but I'll create separate issues for those.

OAuth Scopes?

Hey there, thanks a lot for the package, been trying it out an app right now, and it seems to work great.

I could understand how should I use it, implement it with OAuth Scopes, couldn’t find anything in the docs.

Reading FastAPI docs, I’m guessing it shouldn’t be that much complex, but not really sure tbh. Totally willing to contribute as well, but I think I would need some
guidance on how to implement this feature.

Anyways, thanks a lot!

No version given for pyjwt dependency

fastapi-login does not have a version dependency for pyjwt. The change in f31b671 however expects you to have version >= 2.0, as before, jwt.encode returned the token as bytes, not as str.

Propsed fix would be to set the version to ^2.0 instead of *.
Alternatively, if support for all version is still wanted, you could also check the return type of jwt.encode and cast it to str.

refresh token function

My current solution is:

ACCESS_TOKEN_SCOPE = "access"
REFRESH_TOKEN_SCOPE = "refresh"

def create_access_token(manager: LoginManager, data: dict):
    # data.update({"_type": ACCESS_TOKEN_SCOPE})
    return manager.create_access_token(
        data=data,
        expires=timedelta(minutes=120),
        scopes=[ACCESS_TOKEN_SCOPE],
    )


def create_refresh_token(manager: LoginManager, data: dict):
    # data.update({"_type": REFRESH_TOKEN_SCOPE})
    return manager.create_access_token(
        data=data,
        expires=timedelta(days=15),
        scopes=[REFRESH_TOKEN_SCOPE],
    )

It'll be better to add some handy functions to get refresh token :)

Token expiration Template

Whenever the token is expired I just get a 401 {"detail":"Invalid credentials"}
How could I redirect to login page the same way we do with.

class NotAuthenticatedException(Exception):
    pass

@app.exception_handler(NotAuthenticatedException)
def auth_exception_handler(request: Request, exc: NotAuthenticatedException):
    """
    Redirect the user to the login page if not logged in
    """
    return RedirectResponse(url='/login')

manager.not_authenticated_exception = NotAuthenticatedException```

[Suggestion] custom exception

Now the exception is always InvalidCredentialsException:

def __init__(self, ...):
    ...
    self._not_authenticated_exception = InvalidCredentialsException

not_authenticated_exception has a setter, but auto_error is set False at the same time.

    @not_authenticated_exception.setter
    def not_authenticated_exception(self, value: Exception):
        """
        Setter for the Exception which raises when the user is not authenticated.
        Sets `self.auto_error` to False in order to raise the correct exception.

        Args:
            value (Exception): The Exception you want to raise
        """
        assert issubclass(value, Exception)  # noqa
        self._not_authenticated_exception = value
        # change auto error setting on OAuth2PasswordBearer
        self.auto_error = False

Sometimes users may want to inject a custom exception to be raised and handled by FastAPI exception handlers, so what about add an argument like custom_exception: Exception = None and remove not_authenticated_exception.setter as well as auto_error stuff?

Can't use user=Depends(manager)

As stated, I can login fine, but when I try to protect a route:

TypeError: get_by_email() missing 1 required positional argument: 'email'

My function to retrieve the user:

@classmethod
@manager.user_loader()
async def get_by_email(cls, email) -> 'UserModel':
    return await engine.find_one(cls, cls.email == email)

Login:

@router.post('/login')
async def login(data: OAuth2PasswordRequestForm = Depends()):
    from app.models.user import UserModel

    email = data.username
    password = data.password

    user = await UserModel.get_by_email(email)
    if not user:
        raise InvalidCredentialsException
    elif verify_password(password, user.password):
        raise InvalidCredentialsException

    access_token = manager.create_access_token(
        data={'sub': email}, expires=timedelta(days=365)
    )
    return {'token': access_token}

Protected Route:

@router.post('/user/watcher')
async def create_watcher(watcher: Watcher, user=Depends(manager)):
    await user.add_watcher(**watcher.dict())

What am I missing here?

TypeError: the first argument must be callable

My program was developed based on fastapi_login==1.6.2

Today, I upgrade fastapi_login to the latest version: 1.7.0. When I login, it raises error.

some of the error code:

2021-09-23 14:58:04 | TypeError: the first argument must be callable
-- | --
  |   | 2021-09-23 14:58:04 | self._user_callback = ordered_partial(callback, *args, **kwargs)
  |   | 2021-09-23 14:58:04 | File "/usr/local/lib/python3.8/site-packages/fastapi_login/fastapi_login.py", line 147, in decorator
  |   | 2021-09-23 14:58:04 | user = load_user(name)

The function user_loader in fastapi_login.py of version 1.6.2 is:

    def user_loader(self, callback: Union[Callable, Awaitable]) -> Union[Callable, Awaitable]:
        self._user_callback = callback
        return callback

However, In the latest version, it becomes to:

def user_loader(self, *args, **kwargs) -> Union[Callable, Awaitable]:
        def decorator(callback: Union[Callable, Awaitable]):
            """
            The actual setter of the load_user callback
            Args:
                callback (Callable or Awaitable): The callback which returns the user
            Returns:
                Partial of the callback with given args and keyword arguments already set
            """
            self._user_callback = ordered_partial(callback, *args, **kwargs)
        return decorator

You add the function ordered_partial here cause my problem. But When I refer to the release note, I did not find any notice for this change, only find a pull request note in #56 . It seems that your code is not backwards compatible. @MushroomMaula

STATUS_PHRASES[status_code] KeyError: None

I am trying to set a cookie for a user. When all checks are successful, return response returns the following error:

ERROR:uvicorn.error:Exception in ASGI application
Traceback (most recent call last):
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 396, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\fastapi\applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
    raise exc from None
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\cors.py", line 142, in simple_response
    await self.app(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    raise exc from None
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\routing.py", line 44, in app
    await response(scope, receive, send)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\responses.py", line 132, in __call__
    await send(
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\exceptions.py", line 68, in sender
    await send(message)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\cors.py", line 167, in send
    await send(message)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\starlette\middleware\errors.py", line 156, in _send
    await send(message)
  File "E:\Проекты\Веб\fastapi\env\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 471, in send
    reason = STATUS_PHRASES[status_code]
KeyError: None

This is the authorization method itself. If I return a commented out return (c access_token), then everything is fine.
If I return a return response, I get the above error.

@routers.post('/users/signin')
async def user_signin(response: Response, data: OAuth2PasswordRequestForm = Depends()):
    """
    Создаём нового пользователя
    :param user: схематика
    :return:
    """
    errors = {}

    if not await validators.username_or_email_exists(data.username):
        errors['username'] = 'Пользователь не найден!'

    if not await services.password_check(data.username, data.password):
        errors['password'] = 'Данный пароль не подходит к учётной записи!'

    if not await services.email_activation_check(data.username):
        errors['is_email_activation'] = 'Вы не активировали аккаунт по ссылке в почте!'

    if (errors == {}):
        access_token = manager.create_access_token(
            data = dict(sub=data.username)
        )
        manager.set_cookie(response, access_token)
        #return {'access_token': access_token, 'token_type': 'bearer'}
        return response
    return errors

I don't understand what's going on, this part of the code is identical to your example.

How to do partial access ?

How can you do something like this -
User can access the endpoint but depending on whether he is logged in or not, the responses will be different?

Unauthorized even cookie has been set

Im using Jinja2 for login page and manager with cookies, even cookie has been set successfully, access to protected page get 401 status

below are some of my code

app = FastAPI()
manager = LoginManager(SECRET,'/login', use_cookie=True)
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

@manager.user_loader
def load_user(email: str):  # could also be an asynchronous function
    user = Users.get(db,Users.c.email.like(email))
    if user:
        logger.info('load user : '+user.email)
        return True
    else:
        logger.error('load user not found')    
        return None
@app.get("/")
async def index(request:Request):
    context = {"request":request}
    return templates.TemplateResponse('index.html', context)

@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
    if exc.status_code == 404:
        return templates.TemplateResponse('page-404.html', {'request': request},  status_code=exc.status_code)
    elif exc.status_code == 403:
        return templates.TemplateResponse('page-403.html', {
            'request': request,
            'detail': exc.detail
        },  status_code=exc.status_code)        
    elif exc.status_code == 500:
        return templates.TemplateResponse('page-500.html', {
            'request': request,
            'detail': exc.detail
        },  status_code=exc.status_code)
    else:
        # Generic error page
        return templates.TemplateResponse('page-500.html', {
            'request': request,
            'detail': exc.detail
        },  status_code=exc.status_code)    

@app.get("/login")
async def login(request:Request):
    context = {"request":request}
    return templates.TemplateResponse('sign-in.html', context)
    
@app.post("/login")    
async def login_post(email: str = Form(...), password: str = Form(...)):
    user = Users.get(db,Users.c.email.like(email) & Users.c.password.like(password))
    if user:
        access_token = manager.create_access_token(data={'sub': email})
        response = RedirectResponse(url='/dashboard', status_code=status.HTTP_302_FOUND)
        manager.set_cookie(response, access_token)
        return response
    else:
        raise InvalidCredentialsException
    
@app.get("/dashboard")    
async def dashboard(user=Depends(manager)):
    #return {'user': user}
    return templates.TemplateResponse('dashboard.html')

Exception thrown trying to decode jwt

File "./api/routes.py", line 38, in login
    token = login_manager.create_access_token(data=data, expires_delta=timedelta(days=30))
  File "/path/to/my/.venv/lib/python3.8/site-packages/fastapi_login/fastapi_login.py", line 165, in create_access_token
    return encoded_jwt.decode()
AttributeError: 'str' object has no attribute 'decode'

It looks like that line of code is assuming the encoded_jwt is bytes, but it looks like that method returns a string.

My Pipfile.lock resolved pyjwt to version ==2.0.0, I suppose this is a breaking change from 1.7.1, and I should restrict my Pipfile accordingly?

Admin

Hi I'm planning to use fastapi_login in a project.
I have found the:

    user = request.state.user
    if user is None:
        raise HTTPException(401)
    # assuming our user object has a is_admin property
    elif not user.is_admin:
        raise HTTPException(401)
    else:
        return user

But i don't understand how is_admin changed/set?

Returns Invalid Credentials every time

I am new to FastAPI and Python and I really like using this library. But for some reason it throws the invalid credentials error every time I try to request a protected route.

The thing is, I can log in successfully, I also get the token. But when I use that token as a bearer token in the header or use the FastAPI interface to send a request, it behaves the same.

image

I guess I might have done something wrong. This is my code.

Also the query_user function is marked async because I prev used my database to get the user but even when I tried it with dummy user, it still behaves the same.

from fastapi import Depends, FastAPI
from fastapi.requests import Request
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_login import LoginManager
from fastapi_login.exceptions import InvalidCredentialsException
from passlib.hash import bcrypt

DB = {
    "users": {
        "[email protected]": {
            "name": "John Doe",
            "password": "hunter2",
        }
    }
}
SECRET = "my-secret"

app = FastAPI()
manager = LoginManager(SECRET, token_url="/login")


@manager.user_loader()
async def query_user(username: str):
    return DB["users"].get(username)


@app.get("/protected")
def protected_route(user=Depends(manager)):
    return {"user": user}

@app.post("/login")
async def login(data: OAuth2PasswordRequestForm = Depends()):
    username = data.username
    password = data.password

    user = await query_user(username)

    if user is None:
        raise InvalidCredentialsException
    # elif not bcrypt.verify(password, user.password):
    elif password != user["password"]:
        raise InvalidCredentialsException

    access_token = manager.create_access_token(data={"username": username})
    return {"access_token": access_token, "token_type": "bearer"}

exception not showing up in html template

Hello, I'm new to API and your library in general.

I'm passing the InvalidCredentialsException as you suggested in your readme. However while the exception is working, the exception is throwing a blank page on my browser, (with the exception message) instead of the html with my login page. Is there way you can help?

TypeError: catching classes that do not inherit from BaseException is not allowed

It happens when the token is not correct, for example, a random string token.

INFO:     127.0.0.1:51577 - "POST /auth/refresh HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 369, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 59, in __call__
    return await self.app(scope, receive, send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/routing.py", line 580, in __call__
    await route.handle(scope, receive, send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/routing.py", line 241, in handle
    await self.app(scope, receive, send)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/starlette/routing.py", line 52, in app
    response = await func(request)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/fastapi/routing.py", line 206, in app
    solved_result = await solve_dependencies(
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 548, in solve_dependencies
    solved = await call(**sub_values)
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/fastapi_login/fastapi_login.py", line 346, in __call__
    if not self.has_scopes(token, security_scopes):
  File "/Users/user/my-api/venv/lib/python3.9/site-packages/fastapi_login/fastapi_login.py", line 308, in has_scopes
    except self.not_authenticated_exception as e:
TypeError: catching classes that do not inherit from BaseException is not allowed

Encoding & Decoding Tokens with RS256 (RSA)

LoginManager only accept single secret parameter in str type, how can it use private key to sign and public key to verify?

It seems only possible solution is using two different instances?

sign_manager = LoginManager(secret=private_key, ...)
verify_manager = LoginManager(secret=public_key, ...)

token = sign_manager.create_access_token(...)
payload = verify_manager._get_payload(token)

Or i am misunderstanding?

Get current user without throwing an error

Hi, great package. Is there a way to get the current user without throwing the NotAuthenticatedException. Just get the user or None in my case

Writing a custom auth_exception_handler doesn't cut it for me because it expects a callable to be returned so I can't return None. This might look trivial but in the case where I'm building a web app and my responses class are templates not jsonresponses so returning a jsonresponse also doesn't help because internally fastapi_login just returns that to the browser instead of passing that as a return value from calling get_current_user. What I'm saying in essence is when I call

   ` user = await manager.get_current_user(token=request.cookies.get('token'))`

I should get a value for user from the above, whether that be a user object, None, Jsonresponse. Not currently where I only get a user or nothing else(an exception that I can't control or customize)

Error on 1.7.2 for fastapi_login import LoginManager

Hey there,

I am getting an error on 1.7.2 when importing from fastapi_login import LoginManager,
the result I get is:

  File "/usr/local/lib/python3.7/dist-packages/fastapi_login/utils.py", line 11
    def __call__(self, /, *args, **keywords):
                       ^
SyntaxError: invalid syntax

Not sure if the /, is just a placeholder and has not been updated?

Also, how do I pass on args for the user_loader? In the docs it says:

>>> @manager.user_loader(...)  # Arguments and keyword arguments declared here are passed on
>>> def get_user(user_identifier, ...):
...     # get user logic here

Can you give an example? I am not sure if the below would be correct?

@manager.user_loader(session: Session = Depends(get_session))
def get_user_by_email(email: str, db: session):
    user = db.query(Hero).filter(Hero.email == email).first()
    return user

Regards

OpenAPI Support

Hey, Thanks for building this. Is OpenAPI supported? I can't see an Autheticate button for the same. I'm a noob in this stuffs. Correct me if I'm wrong too

Howto get FASTAPI to use token in requests?

I'm sure the answer is incredibly simple, but I've searched for a week now, and just can't figure this out...

I've followed the guide on using fastapi-login with the standard example:

app = FastAPI()
manager = LoginManager(FASTAPI_LOGIN_SECRET, '/auth/login')
DB = {
    'users': {
        'sander': {
            'name': 'sander',
            'password': 'hello'
        }
    }
}
@manager.user_loader()
def query_user(user_id: str):
    """
    Get a user from the db
    :param user_id: E-Mail of the user
    :return: None or the user object
    """
    return DB['users'].get(user_id)
@app.post('/auth/login')
def login(data: OAuth2PasswordRequestForm = Depends()):
    email = data.username
    password = data.password

    user = query_user(email)
    if not user:
        # you can return any response or error of your choice
        raise InvalidCredentialsException
    elif password != user['password']:
        raise InvalidCredentialsException

    access_token = manager.create_access_token(
        data={'sub': email}
    )
    return {'access_token': access_token}
@app.get('/protected')
def protected_route(user=Depends(manager)):
    return {'user': user}
@app.get("/user/register", response_class=HTMLResponse)
async def home(request: Request):
    return templates.TemplateResponse("user/register.html", {"env": env, "request": request})

My form has:

<form action="/auth/login" method="POST">

Now, visiting /user/register and submitting the form gives me the access_token.

My expectation: If I now visit /protected I should should see the logged in user.
Reality: I get a Not authenticated error.

Now, when I visit the /docs route and try authorization in Swagger, it does work.

I've identified the issue as my browser not sending the Authorization header with the received token.
As there's no example of a frontend for this, I just don't know what to do with the token to make the browser use it on subsequent requests?

The library doesn't work at all

I tried multiple times using this library, but it doesn't seem to work. When I generate the access token, I also do response.set_cookie to save it for the current session. The library correctly retrieves the cookie at the next request, but (I tested it) fastapi.security.utils.get_authorization_scheme_param returns '' as the second parameter of its tuple, which I guess means the provided jwt is invalid, but the access token comes directly from create_access_token! Also, the __call__ method checks if token is not None, but it should actually be if not token, because otherwise weird stuff happens (the custom not authenticated exception is ignored). That's because token equals an empty string, not None

Redirects after login

Hi, I currently have things setup where I use cookies with fastapi_login.

manager = LoginManager(SECRET, tokenUrl='/auth/token', use_cookie=True)
manager.cookie_name = 'oversight_token'

On the auth route I build a response object with a RedirectResponse and set the cookie there.

access_token = manager.create_access_token(
    data=dict(sub=email)
)

response = RedirectResponse(url='/bids')
response.set_cookie(
    key=manager.cookie_name,
    value=f"Bearer {access_token}",
    httponly=True,
    max_age=2147483647,
    expires=2147483647,
)
return response

This works only if the redirect has a POST method, does not work for GET routes. Is that the expected behavior? I'm new to fastapi, sorry for the noob question.

I'm trying to create a login page using jinja2.

I'm trying to create a login page using jinja2.

File "./main.py", line 37, in login
  manager.set_cookie(resp,access_token)

AttributeError: 'LoginManager' object has no attribute 'set_cookie'

What should I do?

from fastapi import FastAPI,Depends
from fastapi.responses import RedirectResponse,HTMLResponse
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_login.exceptions import InvalidCredentialsException
from fastapi.templating import Jinja2Templates
from fastapi_login import LoginManager
import os

SECRET = "your-secret-key"
fake_db = {'[email protected]': {'password': 'hunter2'}}
manager = LoginManager(SECRET, tokenUrl='/auth/token')

app = FastAPI()

pth = os.path.dirname(__file__)
templates = Jinja2Templates(directory=os.path.join(pth, "templates"))

@manager.user_loader
def load_user(email: str):  # could also be an asynchronous function
    user = fake_db.get(email)
    return user

@app.post('/auth/token')
def login(data: OAuth2PasswordRequestForm = Depends()):
    email = data.username
    password = data.password

  user = load_user(email)  # we are using the same function to retrieve the user
  if not user:
      raise InvalidCredentialsException  # you can also use your own HTTPException
  elif password != user['password']:
      raise InvalidCredentialsException
  
  access_token = manager.create_access_token(
      data=dict(sub=email)
  )
  
  resp = RedirectResponse(url="/protected")
  manager.set_cookie(resp,access_token)
  return resp
  # return {'access_token': access_token, 'token_type': 'bearer'}

@app.get('/protected')
def protected_route(user=Depends(manager)):
    with open(os.path.join(pth, "templates/protectedpage.html")) as f:
        return HTMLResponse(content=f.read())


@app.get("/",response_class=HTMLResponse)
def loginwithCreds():
    with open(os.path.join(pth, "templates/login.html")) as f:
        return HTMLResponse(content=f.read())

Redirect back to previous page

Hello I wanted to know if there is any way to redirect back from the page the user was before login.
Situation:

user tries to enter an endpoint: localhost/example , but is protected by fastapi login, so it is redirected to localhost/login.

My question: is there anyway to go back to localhost/example automatically once user is logged in in localhost/login ?
Thanks.

Exception raised on incorrect scopes

For now, if the scope is not correct, it'll just raise an unspecific exception:

async def __call__(self, request: Request, security_scopes: SecurityScopes = None):
    """
    Provides the functionality to act as a Dependency

    Args:
        request (fastapi.Request):The incoming request, this is set automatically
            by FastAPI

    Returns:
        The user object or None

    Raises:
        LoginManager.not_authenticated_exception: If set by the user and `self.auto_error` is set to False

    """

    token = await self._get_token(request)

    if token is None:
        # No token is present in the request and no Exception has been raised (auto_error=False)
        raise self.not_authenticated_exception

    # when the manager was invoked using fastapi.Security(manager, scopes=[...])
    # we have to check if all required scopes are contained in the token
    if security_scopes is not None and security_scopes.scopes:
        if not self.has_scopes(token, security_scopes):
            raise self.not_authenticated_exception

    return await self.get_current_user(token)

This can be improved by introducing another exception. Or maybe we may also improve the class to let users handle the payload themselves?

Cookie support

I was wondering if you had any plans to add refresh token support?

I would like to have a short time which the access token is valid and have the ability to refresh the access token once it expires.

I was also wondering if you've thought about adding cookie support so that cookies would be set by FastAPI in a similar way to how flask-jwt-extended does things: https://flask-jwt-extended.readthedocs.io/en/stable/tokens_in_cookies/

Allow blacklist jwt

I want to allow user to login and use the application from one device only.
I noticed that the current implementation of the user_loader only receives one param the decoded username, making it impossible to access the actual token and compare it

i'm suggesting the following change to give access to the token

line 229: user = await self._load_user(user_identifier, token)

line 236: async def _load_user(self, identifier: typing.Any ,token: str):

if inspect.iscoroutinefunction(self._user_callback):
            user = await self._user_callback(identifier. token)
        else:
            user = self._user_callback(identifier, token)

        return user

now my custom implementation looks like this

@manager.user_loader(db=get_db())
def query_user(username: str, token:str,  db: SessionLocal):
    if blacklisted_jwt[token]:
        return None
    if isinstance(db, types.GeneratorType):
        db = next(get_db())
    user: UserModel = db.query(UserModel).filter(UserModel.username==username).first()
    return user

401 ERROR - Cookie has wrong format

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?

Value of the user parameter in path operations?

Hi, I stumbled upon this project while developing a backend with FastAPI and I'm amazed by the ease of use and completeness of this work. I was wondering what's the actual value of the user parameter defines as user = Depends(manager)? Because I have some functions that, for instance, allow users to delete their account and I need to check if user foobar actually wants to delete his own account or someone else's account (that would be weird wouldn't it). Thanks in advance and Kind regards

Using with sqlachemy.orm.session

Hey thank you for the package, I'm still trying it out the login works fine, this is maybe related heavily to the package but I have some errors raising due to the use of sqlalchemy session, any help is welcome.

I'm trying to pass extra parameters to def load_user(email: str) like this

@auth_manager.user_loader
def load_user(email: str, db: Session):
user = actions.user.get_by_email(db=db, email=email)
return user

i'm passing an sqlalchemy db session to the load_user function which like as said before, works fine on /auth/token, the token is generated. the error raise when trying to access routes protected by the manager:

fastapi_login/fastapi_login.py", line 140
load_user() missing 1 required positional argument: 'db'

is there a way to make the second parameter optional ?

Generator as the `user_loader` argument

Hello,

I have the generator as below which gives me the database session:

def get_db() -> Generator:
    db = None
    try:
        db = SessionLocal(future=True)
        yield db
    finally:
        if db is not None:
            db.close()

I have declared the user_loader the way shown (tried the no-bracket version of providing callable).

@manager.user_loader(db_session=get_db())
def query_user(email: EmailStr, db_session: Session):
    user = db_session.execute(select(User).where(User.email == email)).scalars().first()
    return user

The reason I want the get_db to be user_loader parameter is that my FastAPI routers depend on the get_db and the fastapi-login manager at the same time and inside, I just call the method:

existing_user = query_user(user_in.email, db)

Example route:

@router.get("/users", response_model=List[UserSchema])
def get_users(
    response: Response,
    db: Session = Depends(get_db),
    skip: int = 0,
    limit: int = 100,
    user: User = Depends(manager),
) -> Any:

    if user is None:
        logger.info("Unauthorized user tried to get users list.")
        raise HTTPException(401)
    elif not user.is_superuser:
        logger.info(f"Not-superuser {user} tried to get users list.")
        raise HTTPException(401)

    total = db.scalar(select(func.count(User.id)))
    users = db.execute(select(User).offset(skip).limit(limit)).scalars().all()
    response.headers["Access-Control-Expose-Headers"] = "Content-Range"
    response.headers["Content-Range"] = f"{skip}-{skip + len(users)}/{total}"

    logger.info(f"{user} getting all users with status code {response.status_code}")
    return users

The problem is that when trying to call route depending on the manager, I get the exception:
AttributeError: 'generator' object has no attribute 'execute'

What should I do to get it working? Thanks in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.