mushroommaula / fastapi_login Goto Github PK
View Code? Open in Web Editor NEWFastAPI-Login tries to provide similar functionality as Flask-Login does.
Home Page: https://pypi.org/project/fastapi-login
License: MIT License
FastAPI-Login tries to provide similar functionality as Flask-Login does.
Home Page: https://pypi.org/project/fastapi-login
License: MIT License
Im currently using the manager with cookies. But I want to redirect to '/' when someone who is logged in tries to go to '/login' page.
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?
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?
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?
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 !!!
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)
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?
very cool but how to build an async user_loader?
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"}
I'm getting this error while trying to install it with fastapi 0.73.0,
fastapi-login 1.7.3 depends on fastapi
`@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`
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!
I've that the code is mostly well typed, but mypy still complains, I think this could be solved adding py.typed
to the root, maybe?
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.
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!
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
.
fastapi_login/fastapi_login/fastapi_login.py
Line 371 in 7372df1
It seems you are comparing set(token_scopes) == set(required_scopes)
But, what if the user has two scope (A, B), while the API need scope (A)?
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 :)
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```
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?
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?
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
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 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?
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')
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?
The simple example index.html should be corrected like this
// send data to the backend route
fetch("/auth/register", {
method: "POST",
body: JSON.stringify(object)
becomes
// send data to the backend route
fetch("/auth/register", {
method: "POST",
body: JSON.stringify(object),
headers: { 'Content-Type': 'application/json' }
See this link for details
fastapi/fastapi#3373
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?
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.
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"}
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?
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
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?
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)
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
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
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?
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
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.
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())
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.
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?
fetch("/auth/register", {
method: "POST",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(object)
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/
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
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?
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
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 ?
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.
Currently the updated docs of version 1.7.0 are not shown.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.