supabase / supabase-py

Python Client for Supabase. Query Postgres from Flask, Django, FastAPI. Python user authentication, security policies, edge functions, file storage, and realtime data streaming. Good first issue.
https://supabase.com/docs/reference/python
MIT License
1.75k stars 207 forks source link

Supabase Storage API Session is None in FastAPI with AuthBearer #869

Open elblogbruno opened 4 months ago

elblogbruno commented 4 months ago

Bug report

Describe the bug

I am using supabase-py with fastapi.

I made a login endpoint that uses sign_in_with_password():

dic = {"email": email, "password": password}
            res = supa.auth.sign_in_with_password(dic)

It returns access_token and refresh_token that then I save on my client. My client does requests with the access_token and I use an AuthBearer on fastapi to validate the token and get current user on every endpoint I have:

class AuthBearer(HTTPBearer):
    def __init__(self, auto_error: bool = True):
        super().__init__(auto_error=auto_error)

    async def __call__(
        self,
        request: Request,
    ):
        credentials: Optional[HTTPAuthorizationCredentials] = await super().__call__(
            request
        )
        self.check_scheme(credentials)
        token = credentials.credentials  # pyright: ignore reportPrivateUsage=none
        return await self.authenticate(
            token,
        )

    def check_scheme(self, credentials):
        if credentials and credentials.scheme != "Bearer":
            raise HTTPException(status_code=401, detail="Token must be Bearer")
        elif not credentials:
            raise HTTPException(
                status_code=403, detail="Authentication credentials missing"
            )

    async def authenticate(
        self,
        token: str,
    ) -> User:
        if verify_token(token):
            # supa.postgrest.auth(token=token) 
            return supa.auth.get_user(jwt=token).user
        else:
            raise HTTPException(status_code=401, detail="Invalid token or api key.")

def get_current_user(user: User = Depends(AuthBearer())) -> User:
    return user

When using storage api and being logged in, session is None so consequent storage requests give errors.

@router.get('/uploads/{sheet_id}/{file_name}')
async def get_uploads(request:Request, sheet_id: str, file_name: str, user = Depends(get_current_user)):
    user_id = str(user.id) 
    key = os.path.join(user_id, sheet_id, file_name) 

    print(f"Getting object {key}")  
    print(supa.auth.get_session()) # prints none
    print(supa.storage.from_("test").list(os.path.join(user_id, sheet_id))) # returns []
    image = supa.storage.from_("test").create_signed_url(key, expires_in=3600) # returns error file not found

There are files in there.

I made a sample file to test:

from supabase import Client 

import dotenv

dotenv.load_dotenv()

client = Client(os.getenv("SUPABASE_URL"), os.getenv("SUPABASE_KEY"))
dic = {"email": os.getenv("SUPABASE_TEST_EMAIL"), "password": os.getenv("SUPABASE_TEST_PASSWORD")}
res = client.auth.sign_in_with_password(dic)
access_token = res.session.access_token
user = client.auth.get_user()

# 5f9dc916-0a06-4c09-a4f0-3c4cf16ca7f2 is user id.

print(client.storage.from_("test").list("5f9dc916-0a06-4c09-a4f0-3c4cf16ca7f2/28c90514-a9c3-4a33-8af1-5dbcca40d04a"))
print(client.storage.from_("test").create_signed_url("5f9dc916-0a06-4c09-a4f0-3c4cf16ca7f2/28c90514-a9c3-4a33-8af1-5dbcca40d04a/thumbnail.jpg", expires_in=86400)) # , options

and this gives correct info!

Maybe I am doing something wrong?

Many thanks Bruno

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Set up FastAPI with supabase-py.
  2. Implement login endpoint and AuthBearer as shown above.
  3. Attempt to access the storage API with an authenticated session.
  4. Observe that supa.auth.get_session() returns None, and storage operations fail.

Expected behavior

supabase-py should maintain the session after authentication, allowing storage API calls to succeed as they do in the standalone script.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

Additional context

Add any other context about the problem here.

silentworks commented 4 months ago

This is likely more related to how FastAPI handles requests than the library itself. You should use some sort of persistent storage setup with FastAPI as sessions aren't shared between requests.

elblogbruno commented 4 months ago

Got it thanks! I will look into the safest way of doing it!

Mamdouh66 commented 2 months ago

I was having a kinda of a similar issue where I only send the access_token to the backend and I would just have RLS problems when communicating with the supabase db.

Because the only way to set the session in the backend is by using the set_session function where you have to pass both the access_token and refresh_token which didn't make sense for my to pass the refresh token from my frontend to my backend, and I was kinda stuck because the get_session function doesn't take any params and we just can't get it.

after lot's of searching a solution that helped my was the following

      client = await create_async_client(
          settings.SUPABASE_PROJECT_URL,
          settings.SUPABASE_API_KEY,
          options=ClientOptions(headers={"Authorization": f"Bearer {access_token}"}),
          )

which is basically when creating the client you have to pass the access_token like the above example, it would help with communicating with the supabase api's. But still even with that the get_session still has the same problem.

idk if that helps

elblogbruno commented 2 months ago

Hi, Many thanks for answering. So you create a new client for every endpoint on every request if I understand correctly? Finally I manage myself to append access token directly to storage object in python to get RLS and authentication working with storage module:

supa: Client = create_client(url, key)

supa.storage.session.headers["Authorization"] = f"Bearer {access_token}" 

image = supa.storage.from_("test").create_signed_url(key, expires_in=3600)

Thanks for your answer Bruno