supabase-py
supabase-py copied to clipboard
Create a client with Auth context of a user
Hi everyone
Is your feature request related to a problem? Please describe.
I am trying to write python cloud function (instead of supabase edge function). I wan't to get caller's identity do proceed database read/write with his RLS context. In JS, this is possible as described in the documentation.
https://supabase.com/docs/guides/functions/auth
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req: Request) => {
try {
// Create a Supabase client with the Auth context of the logged in user.
const supabaseClient = createClient(
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API ANON KEY - env var exported by default.
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
)
With Python client, I couldn't reproduce. I tried:
supa_client = create_client("https://****.supabase.co",
"***anon_api_key***",
ClientOptions().replace(headers={"authorization":"Bearer ***user_session_token***"
}))
I also tried
supa_client = create_client("https://****.supabase.co",
"***anon_api_key***",
}))
supa_client.auth.set_session("***user_session_token***","")
None of this works. After studying the code a bit, I think this may be the problem:
https://github.com/supabase-community/supabase-py/blob/2bba842449ccd0b5f933198c343f54c5a67db7ed/supabase/client.py#L61
https://github.com/supabase-community/supabase-py/blob/2bba842449ccd0b5f933198c343f54c5a67db7ed/supabase/client.py#L208
Authorization token is always overwritten with anon API KEY
options.headers.update(self._get_auth_headers())
def _get_auth_headers(self) -> Dict[str, str]:
"""Helper method to get auth headers."""
# What's the corresponding method to get the token
return {
"apiKey": self.supabase_key,
"Authorization": f"Bearer {self.supabase_key}",
}
Describe the solution you'd like
It should be possible to reproduce JS behavior to create client with Auth context of the user that called the function (logged in user's JWT).
Am I missing something ?
I also did the following tests
url = "*****"
anon_key="*****"
supa_client = create_client(url,
anon_key)
user = supa_client.auth.get_user()
HTTPStatusError Traceback (most recent call last)
File [~/Python/tableclone/.env/lib/python3.11/site-packages/gotrue/_sync/gotrue_base_api.py:113](https://file+.vscode-resource.vscode-cdn.net/Users/vlebert/Python/tableclone/~/Python/tableclone/.env/lib/python3.11/site-packages/gotrue/_sync/gotrue_base_api.py:113), in SyncGoTrueBaseAPI._request(self, method, path, jwt, redirect_to, headers, query, body, no_resolve_json, xform)
106 response = self._http_client.request(
107 method,
108 url,
(...)
111 json=body.dict() if isinstance(body, BaseModel) else body,
112 )
--> 113 response.raise_for_status()
114 result = response if no_resolve_json else response.json()
File [~/Python/tableclone/.env/lib/python3.11/site-packages/httpx/_models.py:749](https://file+.vscode-resource.vscode-cdn.net/Users/vlebert/Python/tableclone/~/Python/tableclone/.env/lib/python3.11/site-packages/httpx/_models.py:749), in Response.raise_for_status(self)
748 message = message.format(self, error_type=error_type)
--> 749 raise HTTPStatusError(message, request=request, response=self)
HTTPStatusError: Client error '401 Unauthorized' for url 'https://jcefjfikurjcjxsspstf.supabase.co/auth/v1/user'
For more information check: https://httpstatuses.com/401
During handling of the above exception, another exception occurred:
AuthApiError Traceback (most recent call last)
Cell In[96], line 1
----> 1 user = supa_client.auth.get_user()
...
116 return xform(result)
117 except Exception as e:
--> 118 raise handle_exception(e)
AuthApiError: invalid claim: missing sub claim
Then
url = "****"
anon_key="****"
token = "****"
client_options = ClientOptions()
client_options.headers.update(headers={
"apikey":anon_key,
"Authorization": f"Bearer {token}"
})
supa_client = create_client(url,
anon_key)
supa_client.auth = supa_client._init_supabase_auth_client(url,client_options)
user = supa_client.auth.get_user()
AttributeError Traceback (most recent call last)
File [~/Python/tableclone/.env/lib/python3.11/site-packages/gotrue/_sync/gotrue_base_api.py:106](https://file+.vscode-resource.vscode-cdn.net/Users/vlebert/Python/tableclone/~/Python/tableclone/.env/lib/python3.11/site-packages/gotrue/_sync/gotrue_base_api.py:106), in SyncGoTrueBaseAPI._request(self, method, path, jwt, redirect_to, headers, query, body, no_resolve_json, xform)
105 try:
--> 106 response = self._http_client.request(
107 method,
108 url,
109 headers=headers,
110 params=query,
111 json=body.dict() if isinstance(body, BaseModel) else body,
112 )
113 response.raise_for_status()
File [~/Python/tableclone/.env/lib/python3.11/site-packages/httpx/_client.py:808](https://file+.vscode-resource.vscode-cdn.net/Users/vlebert/Python/tableclone/~/Python/tableclone/.env/lib/python3.11/site-packages/httpx/_client.py:808), in Client.request(self, method, url, content, data, files, json, params, headers, cookies, auth, follow_redirects, timeout, extensions)
806 warnings.warn(message, DeprecationWarning)
--> 808 request = self.build_request(
809 method=method,
810 url=url,
811 content=content,
812 data=data,
813 files=files,
814 json=json,
815 params=params,
816 headers=headers,
817 cookies=cookies,
...
116 return xform(result)
117 except Exception as e:
--> 118 raise handle_exception(e)
AuthRetryableError: 'dict' object has no attribute 'encode'
In both case, I can't use user's context for RLS when querying a table
And finally
url = "*****"
anon_key="*****"
supa_client = create_client(url,
anon_key)
user = supa_client.auth.get_user(
data = supa_client.auth.sign_in_with_password({"email": "*****, "password": "*****"})
supa_client.auth.get_user()
Works as expected.
But querying a table doesn't seem to use user's auth (0 response, must have been filtered by RLS)
as far i can tell, this is a bug. The workaround is:
supabase: Client = create_client(url, key)
supabase.auth.sign_in_with_password({'email':user,'password':passw})
supabase.postgrest.auth(supabase.auth.get_session().access_token)
doing this RLS works for me.
I am really sad that this is not documented.
I just ran into the same problem with storage. It looks like it all stems from the same issue that you pointed out: In client.py
, the self._get_auth_headers()
call overrides the headers dictionary passed in via the constructor:
- https://github.com/supabase-community/supabase-py/blob/develop/supabase/client.py#L61
- https://github.com/supabase-community/supabase-py/blob/develop/supabase/client.py#L96
My current workaround:
key = auth_header.split(" ")[-1]
supabase = create_client(
supabase_url,
key,
)
It's pretty fragile (since I'm assuming the shape of auth_header
without validating it), but it makes _get_auth_headers()
return the right Authorization
header. So, it should work for all of the APIs without needing to know the user's creds.
_get_auth_headers()
should probably just check for the Authorization
header in client_options
and return it if it's present.
+1
hi bro ,i also encountered it ,and find that the method set_session Work normally with access and refresh_token,refresh_token can not be omitted!
This has been fixed in the latest 2.4.3 release of supabase-py. Please test and let me know in this issue if it's not working as expected. Thanks again for the PR @Garee