I am trying to write a logout function in fastapi. For logging out from server side, I am setting the token expiry time to 0 and sending it to client, expecting that this would invalidate the token right at that movement. However, it is not working as expect and even after logout I am able to access the protected APIs. What is wrong here? It this a good approach to logout in jwt scheme? Please help.
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
import uvicorn
import pytz
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_SECONDS = 200 # expire the token, even though user has not logged out. Change the time for your testing.
utc=pytz.UTC
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe#example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
},
"ravi": {
"username": "ravi",
"full_name": "ravi singh",
"email": "ravi.singh#gmail.com",
"hashed_password": "$2b$12$ODf2vUEfanF3P1JykF0CgO7jafMA9RWCuqZxLUAqCcQJ1FYxxFROC",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice#example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
expires: datetime
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# get the current user from auth token
async def get_current_user(token: str = Depends(oauth2_scheme)):
# define credential exception
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# decode token and extract username and expires data
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
exps:int = payload.get("exp")
# validate username
if username is None:
raise credentials_exception
token_data = TokenData(username=username, expires=exps) # exps of int is converted to datetime type
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
# check token expiration
if exps is None:
raise credentials_exception
if utc.localize(datetime.utcnow()) > token_data.expires:
print("Token is expired.\n")
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
#app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()): # login function to get access token
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(seconds=ACCESS_TOKEN_EXPIRE_SECONDS)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
#app.post("/logout", response_model=Token)
async def logout(current_user: User = Depends(get_current_active_user)): # logout function to delete access token
token_data = TokenData(username=current_user.username, expires=0)
return token_data
#return "User logout sucessful."
#app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
#app.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
return [{"item_id": "Foo", "owner": current_user.username}]
#app.get("/protected_hi")
async def protected_hi(current_user: User = Depends(get_current_active_user)):
return "Hi! How are you? You are in a protected Zone."
#app.get("/unprotected_hi")
async def unprotected_hi():
return "Hi! How are you? You are in an un-protected Zone."
if __name__ == "__main__":
import uvicorn
uvicorn.run('jwt_fourth:app', host="0.0.0.0", port=5000, reload=True)
Related
Here is my code:
from fastapi import (FastAPI, BackgroundTasks, UploadFile,
File, Form, Depends, HTTPException, status, Request)
from tortoise.contrib.fastapi import register_tortoise
from models import (User, Business, Product, user_pydantic, user_pydanticIn,
product_pydantic,product_pydanticIn, business_pydantic,
business_pydanticIn, user_pydanticOut)
# signals
from tortoise.signals import post_save
from typing import List, Optional, Type
from tortoise import BaseDBAsyncClient
from starlette.responses import JSONResponse
from starlette.requests import Request
#authentication and authorization
import jwt
from dotenv import dotenv_values
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm
)
# self packages
from emails import *
from authentication import *
from dotenv import dotenv_values
import math
# user image uploads
# pip install python-multipart
from fastapi import File, UploadFile
import secrets
# static files
from fastapi.staticfiles import StaticFiles
# pillow
from PIL import Image
# templates
from fastapi.templating import Jinja2Templates
# HTMLResponse
from fastapi.responses import HTMLResponse
config_credentials = dict(dotenv_values(".env"))
app = FastAPI()
# static files
# pip install aiofiles
app.mount("/static", StaticFiles(directory="static"), name="static")
# authorization configs
oath2_scheme = OAuth2PasswordBearer(tokenUrl = 'token')
# password helper functions
#app.post('/token')
async def generate_token(request_form: OAuth2PasswordRequestForm = Depends()):
token = await token_generator(request_form.username, request_form.password)
return {'access_token' : token, 'token_type' : 'bearer'}
# process signals here
#post_save(User)
async def create_business(
sender: "Type[User]",
instance: User,
created: bool,
using_db: "Optional[BaseDBAsyncClient]",
update_fields: List[str]) -> None:
if created:
business_obj = await Business.create(
business_name = instance.username, owner = instance)
await business_pydantic.from_tortoise_orm(business_obj)
# send email functionality
await send_email([instance.email], instance)
#app.post('/registration')
async def user_registration(user: user_pydanticIn):
user_info = user.dict(exclude_unset = True)
user_info['password'] = get_password_hash(user_info['password'])
user_obj = await User.create(**user_info)
new_user = await user_pydantic.from_tortoise_orm(user_obj)
return {"status" : "ok",
"data" :
f"Hello {new_user.username} thanks for choosing our services. Please check your email inbox and click on the link to confirm your registration."}
# template for email verification
templates = Jinja2Templates(directory="templates")
#app.get('/verification', response_class=HTMLResponse)
# make sure to import request from fastapi and HTMLResponse
async def email_verification(request: Request, token: str):
user = await verify_token(token)
if user and not user.is_verified:
user.is_verified = True
await user.save()
return templates.TemplateResponse("verification.html",
{"request": request, "username": user.username}
)
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user(token: str = Depends(oath2_scheme)):
try:
payload = jwt.decode(token, config_credentials['SECRET'], algorithms = ['HS256'])
user = await User.get(id = payload.get("id"))
except:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return await user
#app.post('/user/me')
async def user_login(user: user_pydantic = Depends(get_current_user)):
business = await Business.get(owner = user)
logo = business.logo
logo = "localhost:8000/static/images/"+logo
return {"status" : "ok",
"data" :
{
"username" : user.username,
"email" : user.email,
"verified" : user.is_verified,
"join_date" : user.join_date.strftime("%b %d %Y"),
"logo" : logo
}
}
#app.post("/products")
async def add_new_product(product: product_pydanticIn,
user: user_pydantic = Depends(get_current_user)):
product = product.dict(exclude_unset = True)
# to avoid division by zero error
if product['original_price'] > 0:
product["percentage_discount"] = ((product["original_price"] - product['new_price'] )/ product['original_price']) * 100
product_obj = await Product.create(**product, business = user)
product_obj = await product_pydantic.from_tortoise_orm(product_obj)
return {"status" : "ok", "data" : product_obj}
#app.get("/products")
async def get_products():
response = await product_pydantic.from_tortoise_orm(Product.all())
return {"status" : "ok", "data" : response}
#app.get("/products/{id}")
async def specific_product(id: int):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
response = await product_pydantic.from_queryset_single(Product.get(id = id))
print(type(response))
return {"status" : "ok",
"data" :
{
"product_details" : response,
"business_details" : {
"name" : business.business_name,
"city" : business.city,
"region" : business.region,
"description" : business.business_description,
"logo" : business.logo,
"owner_id" : owner.id,
"email" : owner.email,
"join_date" : owner.join_date.strftime("%b %d %Y")
}
}
}
#app.delete("/products/{id}")
async def delete_product(id: int, user: user_pydantic = Depends(get_current_user)):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
if user == owner:
product.delete()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
return {"status" : "ok"}
# image upload
#app.post("/uploadfile/profile")
async def create_upload_file(file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
business = await Business.get(owner = user)
owner = await business.owner
# check if the user making the request is authenticated
print(user.id)
print(owner.id)
if owner == user:
business.logo = token_name
await business.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
#app.post("/uploadfile/product/{id}")
# check for product owner before making the changes.
async def create_upload_file(id: int, file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
#get product details
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
# check if the user making the request is authenticated
if owner == user:
product.product_image = token_name
await product.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
register_tortoise(
app,
db_url='sqlite://database.sqlite3',
modules={'models': ['models']},
generate_schemas = True,
add_exception_handlers = True
)
When im opening up http://127.0.0.1:8000/docs and try the "/registration" request I get the error: Code: 422 Details: Error: Unprocessable Entity`
Response body:
{
"detail": [
{
"loc": [],
"msg": "UNIQUE constraint failed: user.username",
"type": "IntegrityError"
}
]
}
Response headers:
content-length: 95
content-type: application/json
date: Sun,04 Dec 2022 23:22:02 GMT
server: uvicorn
And in the console I get:
127.0.0.1:54566 - "POST /registration HTTP/1.1" 422 Unprocessable Entity
After some debugging I think that the line 86
user_obj = await User.create(**user_info)
Is causing problems, but I can't seem to be able to fix the issue. Feel free to ask for any additional information
UPD: I was followng Princekrampah's guide on youtube, the git link to the repo is: https://github.com/Princekrampah/learningFastAPI/tree/master/shoppingAPI
authentication.py
from datetime import datetime, timedelta
from fastapi import Depends , HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
from jose import JWTError, jwt
from auth.schemas import TokenData, UserBase
from sqlalchemy.orm import Session
from db.database import get_db
from pydantic import ValidationError
from db.models import UserModel
SECRET_KEY = '7f0ef2549adac85716db6ca433a44c79021e829c8c4dd8e7d9363eb9fc800e73'
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login",
scopes={"admin":"create content", "user":"interact with content"}
)
def get_user(email: str,db:Session=Depends(get_db)):
user = db.query(UserModel).filter(UserModel.email == email).first()
if not user:
raise HTTPException(status=status.HTTP_404_NOT_FOUND,
details=f"User with email: {email} not found")
return user
async def get_current_user(
security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = f"Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
raise credentials_exception
user = get_user(email=token_data.email)
if user is None:
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_data.scopes:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="not enough permission",
headers={"WWW-Authenticate": authenticate_value},
)
return user
def get_current_active_user(
current_user:UserBase = Security(get_current_user, scopes=["admin"])):
if not current_user:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
def create_access_token(data:dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode,SECRET_KEY,algorithm=ALGORITHM)
return encoded_jwt
def verify_token(token:str,credentials_exception):
try:
payload = jwt.decode(token,SECRET_KEY,algorithms=ALGORITHM)
email:str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
return token_data
except JWTError:
raise credentials_exception
login
from fastapi import APIRouter, Depends,HTTPException,status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from sqlalchemy.orm import Session
from db.models import UserModel
from db.database import get_db
from . import hashing
from auth.oauth import create_access_token
from .hashing import get_password_hash
from .schemas import UserBase
router = APIRouter(tags=['authentication'])
#router.post('/login')
async def login(request:OAuth2PasswordRequestForm=Depends(),db:Session=Depends(get_db)):
user = db.query(UserModel).filter(UserModel.email == request.username).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail="Invalid Credentials")
if not hashing.verify_password(request.password,user.password):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,detail="Invalid Password")
access_token = create_access_token(data={"sub":user.username,"scopes":request.scopes})
return {"access_token":access_token,"token_type":"bearer"}
#router.post('/register')
async def register(request:UserBase,db:Session=Depends(get_db)):
new_user = UserModel(
email = request.email,
username = request.username,
password = get_password_hash(request.password),
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
endpoints
from fastapi import APIRouter, Depends, Security
from auth.oauth import get_current_active_user
from routers.schemas import BikeBase
from sqlalchemy.orm import Session
from db.database import get_db
from db.models import BikeModel
from .crud import add_bike
from auth.schemas import UserBase
router = APIRouter(
prefix='/bike',
tags = ['bike']
)
#router.post('/add')
def add_bike(request:BikeBase,user:UserBase=Depends(get_current_active_user),db:Session=Depends(get_db)):
bike = db.query(BikeModel).filter(BikeModel.model==request.model).first()
if bike is None:
return add_bike(request,user)
if request.model in bike:
raise Exception("Bike Model Already present")
return add_bike(request,user)
#router.get('/check')
def check(user:UserBase=Security(get_current_active_user, scopes=["admin"])):
return {"data":"scope working"}
def add_bike(request:BikeBase,user=Security(get_current_active_user, scopes=["admin"]),db:Session=Depends(get_db)):
new_bike = BikeBase(
brand = request.brand.lower(),
model = request.model.lower(),
price = request.price,
mileage = request.mileage,
max_power = request.max_power,
rating = request.rating
)
db.add(new_bike)
db.commit()
db.refresh(new_bike)
return new_bike
On the #router.post('/add') getting the error:
File "/home/raj/code/bike-forum-fastapi/./auth/oauth.py", line 24, in get_user
user = db.query(UserModel).filter(UserModel.email == email).first()
AttributeError: 'Depends' object has no attribute 'query'
When i change some endpoints to async the error changes something with coroutine, i cant remember how to produce it
#router.check('/check') endpoint works fine with scope and permission, but produces error at #router.post('/add')
i new to fastapi, the auth code is directly copied from fastapi-docs, but theres a hardcoded fake database for get_user in docs, i cant figure how to do it with a real database
I have researched similar questions but can't find an answer that works for me.
I would like to get the username or user_id from a session when a user connects to a websocket.
This is what I have in consumers.py:
class PracticeConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print('session data', self.scope['user'])
await self.send({"type": "websocket.accept", })
...
#database_sync_to_async
def get_user(self, user_id):
try:
return User.objects.get(username=user_id).pk
except User.DoesNotExist:
return AnonymousUser()
This is my asgi.py:
"""
ASGI config for restapi project.
It exposes the ASGI callable as a module-level variable named application.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'signup.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
)
)
})
and the user login function + token sent when user logs in:
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
authenticate_kwargs = {
self.username_field: attrs[self.username_field],
"password": attrs["password"],
}
try:
authenticate_kwargs["request"] = self.context["request"]
except KeyError:
pass
user = authenticate(**authenticate_kwargs)
if not user:
return {
'user': 'Username or password is incorrect',
}
token = RefreshToken.for_user(user)
# customizing token payload
token['username'] = user.username
token['first_name'] = user.first_name
token['last_name'] = user.last_name
token['country'] = user.profile.country
token['city'] = user.profile.city
token['bio'] = user.profile.bio
token['photo'] = json.dumps(str(user.profile.profile_pic))
user_logged_in.send(sender=user.__class__, request=self.context['request'], user=user)
if not api_settings.USER_AUTHENTICATION_RULE(user):
raise exceptions.AuthenticationFailed(
self.error_messages["no_active_account"],
"no_active_account",
)
return {
'refresh': str(token),
'access': str(token.access_token),
}
Whenever I print out self.scope['user'] upon connecting, I get AnonymousUser
UPDATE
I tried writing some custom middleware to handle simple JWT authentication:
#database_sync_to_async
def get_user(validated_token):
try:
user = get_user_model().objects.get(id=validated_token["user_id"])
print(f"{user}")
return user
except User.DoesNotExist:
return AnonymousUser()
class JwtAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
try:
UntypedToken(token)
except (InvalidToken, TokenError) as e:
print(e)
return None
else:
decoded_data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print(decoded_data)
scope["user"] = await get_user(validated_token=decoded_data)
return await super().__call__(scope, receive, send)
def JwtAuthMiddlewareStack(inner):
return JwtAuthMiddleware(AuthMiddlewareStack(inner))
However, this gives me this error:
File "C:\Users\15512\Desktop\django-project\peerplatform\signup\middleware.py", line 37, in __call__
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
KeyError: 'token'
Is it possible to add a custom field into the OAuth2PasswordRequestForm? I need the content of the field to be returned in the token as "rentalpoint". Now in the return "rentalpoint" holds a fixed string "rentalpoint": "cologne" but i want to replace it with something like this: "rentalpoint": request.rentalpoint. How can i do that?
#router.post('/')
def login(request: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(database.get_db)):
user = db.query(models.User).filter(models.User.email == request.username).first()
if not user:
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND, detail='Invalid credentials')
if not Hash.verify(user.password, request.password):
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND, detail='Incorrect password')
access_token = JWTtoken.create_access_token(data={"sub": user.email})
return {"access_token": access_token, "token_type": "bearer", "rentalpoint": "cologne", "user_id": request.username}
I need to create a API with a route that is able to recognize if the current user is the one indicated in the request or not (also no auth should be valid)
For the others paths I followed https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/ and everything work with Bearer with JWT tokens like this
user: User = Depends(get_current_active_user)
Modifying the methods provided by the docs I tried with
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth")
async def get_user_or_none(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
"""
Return the current active user if is present (using the token Bearer) or None
"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
return None
except JWTError:
return None
# check user in db
user = crud.get_user(db, username)
if user is None:
return None
return user
#router.get("/api/{user_id}/structure")
async def get_user_structure(
user_id: int,
user = Depends(get_user_or_none),
db: Session = Depends(get_db)
):
# do_something() if user.id == user_id else do_something_else()
but I receive an error
401 Error: Unauthorized {
"detail": "Not authenticated"
}
You need to use a different OAuth2PasswordBearer for these optionally authenticated endpoints with auto_error=False, e.g.
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth", auto_error=False)
async def get_user_or_none(db: Session = Depends(get_db), token: str = Depends(optional_oauth2_scheme)):
...
Now the token will be None when the Authorization header isn't provided, resulting in your desired behavior.