Supabase Auth session works, but RLS treats the request as unauthenticated in Flask
What I’m trying to do
I’m building a Flask backend that calls Supabase via the async Python client. The user successfully signs in and auth.get_user() returns the expected profile, but any subsequent SELECT on the users table returns zero rows. RLS seems to think the request is anonymous.
Environment
- OS: MacOS Sequoia 15.5
- Supabase client: supabase>=2.16.0
- Python: 3.13.5
- Flask: 3.1.1
Code snippets
Client setup
Sign-in
Read all users
Read users route
RLS policy
What’s going wrong
Even though the session is live and the JWT is attached, Postgres behaves as if the role is anon instead of authenticated, so the SELECT is filtered out by RLS.
What I’m looking for
1. An explanation of what exactly causes this problem.
2. Any pointers on which extra header / cookie / claim Supabase expects when using the async Python client in Flask.
3. Known gotchas when combining Flask session storage with the supabase library.
4. A minimal working example (with SUPABASE_ANON_KEY) that proves RLS + Supabase Auth with Python works on the server side.
6 Replies
What does your
FlaskSessionStorage
look like? also Flask is a sync framework but you said you are using the async supabase client. Please also share your full imports as code partials don't help give much context.1.
from flask import session
from gotrue import AsyncSupportedStorage # type: ignore[]
class FlaskSessionStorage(AsyncSupportedStorage):
def init(self) -> None:
self.storage = session
async def get_item(self, key: str) -> str | None:
return self.storage.get(key)
async def set_item(self, key: str, value: str) -> None:
self.storage[key] = value
async def remove_item(self, key: str) -> None:
self.storage.pop(key, None)
2. Flask supports asynchronous methods and can run as an ASGI server. https://flask.palletsprojects.com/en/stable/async-await/
3.
app startup
app = asyncio.run(create_app())
asgi_app = WsgiToAsgi(app)
if name == "main":
config = Config()
config.bind = ["0.0.0.0:8000"]
asyncio.run(serve(asgi_app, config)
register handler
class UsersRoutes:
logger = getLogger(name)
def init(self, db_client: SupabaseClient) -> None:
"""Initialize users routes."""
self.users = UsersRoute.as_view("users", db_client=db_client)
self.user = UserRoute.as_view("user", db_client=db_client)
def initialize(self, app: Flask) -> None:
"""Add users routes to the application."""
users_bp = Blueprint("users", name, url_prefix="/users")
users_bp.add_url_rule(
"/",
view_func=self.users,
methods=["GET"],
)
users_bp.add_url_rule(
"/<uuid:user_id>",
view_func=self.__user,
methods=["GET", "PATCH"],
)
Everything else remains unchanged and fully covers the logic for retrieving a user.
If you need any additional code snippets, let me know
The code looks ok to me, not sure why it's not working. I have a flask example app but it's not async and it works perfectly fine. Maybe try and create a minimal reproducible example repo and sare it here.
My example is here https://github.com/silentworks/flask-notes, haven't updated it in a while but it should all still work even with the latest version of the python library.
Did you use ANON KEY in your application?
Yes anon key is used
Solved the RLS issue
We now spin up fresh clients per request, bind the current Supabase session to them, and stash them in g.
Every query runs with the correct JWT, so RLS passes.
Result: each request carries its own token-aware DatabaseClient, so CRUD operations work under RLS without token leakage across concurrent requests.