Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: do not send non-JWTs in Authorization header #977

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions supabase/_async/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from storage3.constants import DEFAULT_TIMEOUT as DEFAULT_STORAGE_CLIENT_TIMEOUT
from supafunc import AsyncFunctionsClient

from supabase.lib.helpers import is_valid_jwt

from ..lib.client_options import AsyncClientOptions as ClientOptions
from .auth_client import AsyncSupabaseAuthClient

Expand Down Expand Up @@ -278,9 +280,10 @@ def _create_auth_header(self, token: str):

def _get_auth_headers(self, authorization: Optional[str] = None) -> Dict[str, str]:
if authorization is None:
authorization = self.options.headers.get(
"Authorization", self._create_auth_header(self.supabase_key)
)
if is_valid_jwt(self.supabase_key):
authorization = self.options.headers.get(
"Authorization", self._create_auth_header(self.supabase_key)
)

"""Helper method to get auth headers."""
return {
Expand All @@ -291,13 +294,16 @@ def _get_auth_headers(self, authorization: Optional[str] = None) -> Dict[str, st
def _listen_to_auth_events(
self, event: AuthChangeEvent, session: Optional[Session]
):
access_token = self.supabase_key
default_access_token = (
self.supabase_key if is_valid_jwt(self.supabase_key) else None
)
access_token = default_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.supabase_key
access_token = session.access_token if session else default_access_token

self.options.headers["Authorization"] = self._create_auth_header(access_token)
asyncio.create_task(self.realtime.set_auth(access_token))
Expand Down
16 changes: 11 additions & 5 deletions supabase/_sync/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from storage3.constants import DEFAULT_TIMEOUT as DEFAULT_STORAGE_CLIENT_TIMEOUT
from supafunc import SyncFunctionsClient

from supabase.lib.helpers import is_valid_jwt

from ..lib.client_options import SyncClientOptions as ClientOptions
from .auth_client import SyncSupabaseAuthClient

Expand Down Expand Up @@ -277,9 +279,10 @@ def _create_auth_header(self, token: str):

def _get_auth_headers(self, authorization: Optional[str] = None) -> Dict[str, str]:
if authorization is None:
authorization = self.options.headers.get(
"Authorization", self._create_auth_header(self.supabase_key)
)
if is_valid_jwt(self.supabase_key):
authorization = self.options.headers.get(
"Authorization", self._create_auth_header(self.supabase_key)
)

"""Helper method to get auth headers."""
return {
Expand All @@ -290,13 +293,16 @@ def _get_auth_headers(self, authorization: Optional[str] = None) -> Dict[str, st
def _listen_to_auth_events(
self, event: AuthChangeEvent, session: Optional[Session]
):
access_token = self.supabase_key
default_access_token = (
self.supabase_key if is_valid_jwt(self.supabase_key) else None
)
access_token = default_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.supabase_key
access_token = session.access_token if session else default_access_token

self.options.headers["Authorization"] = self._create_auth_header(access_token)

Expand Down
41 changes: 41 additions & 0 deletions supabase/lib/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import re
from typing import Dict

BASE64URL_REGEX = r"^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$"


def is_valid_jwt(value: str) -> bool:
"""Checks if value looks like a JWT, does not do any extra parsing."""
if not isinstance(value, str):
return False

# Remove trailing whitespaces if any.
value = value.strip()

# Remove "Bearer " prefix if any.
if value.startswith("Bearer "):
value = value[7:]

# Valid JWT must have 2 dots (Header.Paylod.Signature)
if value.count(".") != 2:
return False

for part in value.split("."):
if not re.search(BASE64URL_REGEX, part, re.IGNORECASE):
return False

return True


def check_authorization_header(headers: Dict[str, str]):
authorization = headers.get("Authorization")
if not authorization:
return

if authorization.startswith("Bearer "):
if not is_valid_jwt(authorization):
raise ValueError(
"create_client called with global Authorization header that does not contain a JWT"
)

return True