supabase-py icon indicating copy to clipboard operation
supabase-py copied to clipboard

🤓Add optional key_words:access_token to creat_client let auth user easier without signing in

Open AtticusZeller opened this issue 2 years ago • 0 comments

Is your feature request related to a problem? Please describe. here's about my understanding on recent issues and my explanation on solutions in PR #656 #658 #663 #645


in cannot authenticate user #645 in short, i think it shows up for doing nothing on refresh the token passed to postgrest,it fixed in v2.3.3 by updating

    def _listen_to_auth_events(self, event: AuthChangeEvent, session: Session | None):
        """listen to auth events and update auth token"""
        access_token = self._access_token
        if event in ["SIGNED_IN", "TOKEN_REFRESHED", "SIGNED_OUT"]:
            # reset postgrest and storage instance on event change
            self._postgrest = None
            self._storage = None
            self._functions = None
            access_token = session.access_token if session else self._access_token
        self._auth_token = self._create_auth_header(access_token)

,self._auth_token would be update on new events and also as the new header of postgrest etc


in failed to get_session after create_client by accees_token as supabase_key #663

firstly , client.auth.get_session() would retrun None ,because in AsyncGoTrueClient , get_session() only works proper as we saved session in self._storage,which means we need technically client.auth.sign _in_xxx() or client.auth.set_session()

    async def get_session(self) -> Union[Session, None]:
      ...
        current_session: Union[Session, None] = None
        if self._persist_session:
            maybe_session = await self._storage.get_item(self._storage_key)
            ....
        else:
            current_session = self._in_memory_session
        if not current_session:
            return None
        ...

secondly, i will fail quickly on calling postgrest via create_client passed accees_token as supabase_key for two reasons,

  1. called client._auth_token = await client._get_token_header() on createing client(u do not signed in at that moment) so that the client.auth.get_session() in client._get_token_header() must return None,so acees_token set None for postgrest unless u sign in later
  2. we called client.init,and it set apiKey in headers as access_token that got after user signed in ,and is not valided for apiKey after tests
# in `client.init`,
...
options.headers.update(self._get_auth_headers())
self.options = options
...
def _get_auth_headers(self) -> Dict[str, str]:
        """Helper method to get auth headers."""
        return {
            "apiKey": self.supabase_key,
            "Authorization": f"Bearer {self.supabase_key}",
        }

in Clients should take an optional supabase_token (workaround provided) #658 i do agree that should take an optional supabase_token ,we can not auth only by access_token (actually in code,it means not able to set self._auth_token by passing access_token explicitly now)


my solutions on just passing token without changing supabase-py to auth user after tests should do create_client with normal key and url firstly

  1. pass access_token manually like
self.auth.postgrest.auth(access_token)
# and then do crud operations ...
  1. call self.auth.set_session() manually like
# firstly should get aceess_token and refresh_token from front_end
self.auth.set_session(aceess_token,refresh_token )

my solutions on changeing code of supabase-py in PR #656

  1. add optional access_token key words and set is as default key, if None, set supabase_key as self. _access_token, and it will default bt passed to self._create_auth_header()
# in init
def __init__(
        self,
        supabase_url: str,
        supabase_key: str,
        access_token: Union[str, None] = None,
        options: ClientOptions = ClientOptions(storage=AsyncMemoryStorage()),
    ):
# ....
# only can be set by init instance
self._access_token = access_token if access_token else supabase_key
# will be modified by auth state change
self._auth_token = self._create_auth_header(self._access_token)
# ...
# listen to event to refresh access_token if we explicitly sign in
    def _listen_to_auth_events(self, event: AuthChangeEvent, session: Session | None):
        """listen to auth events and update auth token"""
        access_token = self._access_token
        if event in ["SIGNED_IN", "TOKEN_REFRESHED", "SIGNED_OUT"]:
            # reset postgrest and storage instance on event change
            self._postgrest = None
            self._storage = None
            self._functions = None
            access_token = session.access_token if session else self._access_token
        self._auth_token = self._create_auth_header(access_token)
  1. add self._update_auth_headers() to update self.options.headers and it also will be setted as headers that passes to postgrest
    def _update_auth_headers(self) -> None:
        """Helper method to get auth headers."""
        new_headers = {
            "apiKey": self.supabase_key,
            **self._auth_token,
        }
        self.options.headers.update(**new_headers)
@property
    def postgrest(self):
        if self._postgrest is None:
            self._update_auth_headers()
            self._postgrest = self._init_postgrest_client(
                rest_url=self.rest_url,
                headers=self.options.headers,
                schema=self.options.schema,
                timeout=self.options.postgrest_client_timeout,
            )

        return self._postgrest

in the end u will find def _get_token_header(self): no where to use in short add optional aceess_token as auth_token in first init client, and update auth_token by listen to events, if access_token is None, default same to supabase_key

AtticusZeller avatar Jan 15 '24 03:01 AtticusZeller