Skip to content

Commit 4f2cccc

Browse files
authored
Merge pull request #275 from supertokens/fix/clear-sesion
fix: Clear session changes for header based auth
2 parents ed905b8 + 92a1104 commit 4f2cccc

File tree

16 files changed

+241
-57
lines changed

16 files changed

+241
-57
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## unreleased
9+
10+
### Breaking changes
11+
12+
- The frontend SDK should be updated to a version supporting the header-based sessions!
13+
- supertokens-auth-react: >= 0.32.0
14+
- supertokens-web-js: >= 0.5.0
15+
- supertokens-website: >= 16.0.0
16+
- supertokens-react-native: >= 4.0.0
17+
- !!!TODO: re-check before release and add mobile SDKs
18+
- Only supporting FDI 1.16
19+
20+
### Added
21+
22+
- Added support for authorizing requests using the `Authorization` header instead of cookies
23+
- Added `get_token_transfer_method` config option
24+
- Check out https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/sessions/token-transfer-method for more information
25+
26+
927
# [0.11.13] - 2023-01-06
1028

1129
- Add missing `original` attribute to flask response and remove logic for cases where `response` is `None`

frontendDriverInterfaceSupported.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_comment": "contains a list of frontend-driver interfaces branch names that this core supports",
33
"versions": [
4-
"1.15"
4+
"1.16"
55
]
66
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
setup(
7272
name="supertokens_python",
73-
version="0.11.13",
73+
version="0.12.0",
7474
author="SuperTokens",
7575
license="Apache 2.0",
7676
author_email="[email protected]",

supertokens_python/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15"]
15-
VERSION = "0.11.13"
15+
VERSION = "0.12.0"
1616
TELEMETRY = "/telemetry"
1717
USER_COUNT = "/users/count"
1818
USER_DELETE = "/user/remove"

supertokens_python/framework/django/django_response.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import json
1515
from datetime import datetime
1616
from math import ceil
17-
from typing import Any, Dict, Union
17+
from typing import Any, Dict, Optional
1818

1919
from supertokens_python.framework.response import BaseResponse
2020

@@ -42,7 +42,7 @@ def set_cookie(
4242
value: str,
4343
expires: int,
4444
path: str = "/",
45-
domain: Union[str, None] = None,
45+
domain: Optional[str] = None,
4646
secure: bool = False,
4747
httponly: bool = False,
4848
samesite: str = "lax",
@@ -69,11 +69,14 @@ def set_status_code(self, status_code: int):
6969
def set_header(self, key: str, value: str):
7070
self.response[key] = value
7171

72-
def get_header(self, key: str):
72+
def get_header(self, key: str) -> Optional[str]:
7373
if self.response.has_header(key):
7474
return self.response[key]
7575
return None
7676

77+
def remove_header(self, key: str):
78+
del self.response[key]
79+
7780
def set_json_content(self, content: Dict[str, Any]):
7881
if not self.response_sent:
7982
self.set_header("Content-Type", "application/json; charset=utf-8")

supertokens_python/framework/fastapi/fastapi_response.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import json
1515
from math import ceil
1616
from time import time
17-
from typing import Any, Dict, Union
17+
from typing import Any, Dict, Optional
1818

1919
from supertokens_python.framework.response import BaseResponse
2020

@@ -44,7 +44,7 @@ def set_cookie(
4444
value: str,
4545
expires: int,
4646
path: str = "/",
47-
domain: Union[str, None] = None,
47+
domain: Optional[str] = None,
4848
secure: bool = False,
4949
httponly: bool = False,
5050
samesite: str = "lax",
@@ -78,9 +78,12 @@ def set_cookie(
7878
def set_header(self, key: str, value: str):
7979
self.response.headers[key] = value
8080

81-
def get_header(self, key: str) -> Union[str, None]:
81+
def get_header(self, key: str) -> Optional[str]:
8282
return self.response.headers.get(key, None)
8383

84+
def remove_header(self, key: str):
85+
del self.response.headers[key]
86+
8487
def set_status_code(self, status_code: int):
8588
if not self.status_set:
8689
self.response.status_code = status_code

supertokens_python/framework/flask/flask_response.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
import json
15-
from typing import Any, Dict, List, Union
15+
from typing import Any, Dict, List, Optional
1616

1717
from supertokens_python.framework.response import BaseResponse
1818

@@ -40,7 +40,7 @@ def set_cookie(
4040
value: str,
4141
expires: int,
4242
path: str = "/",
43-
domain: Union[str, None] = None,
43+
domain: Optional[str] = None,
4444
secure: bool = False,
4545
httponly: bool = False,
4646
samesite: str = "lax",
@@ -59,9 +59,12 @@ def set_cookie(
5959
def set_header(self, key: str, value: str):
6060
self.response.headers.set(key, value)
6161

62-
def get_header(self, key: str) -> Union[None, str]:
62+
def get_header(self, key: str) -> Optional[str]:
6363
return self.response.headers.get(key)
6464

65+
def remove_header(self, key: str):
66+
del self.response.headers[key]
67+
6568
def set_status_code(self, status_code: int):
6669
if not self.status_set:
6770
self.response.status_code = status_code

supertokens_python/framework/response.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# under the License.
1414

1515
from abc import ABC, abstractmethod
16-
from typing import Any, Dict, Union
16+
from typing import Any, Dict, Optional
1717

1818

1919
class BaseResponse(ABC):
@@ -31,19 +31,23 @@ def set_cookie(
3131
# max_age: Union[int, None] = None,
3232
expires: int,
3333
path: str = "/",
34-
domain: Union[str, None] = None,
34+
domain: Optional[str] = None,
3535
secure: bool = False,
3636
httponly: bool = False,
3737
samesite: str = "lax",
3838
):
3939
pass
4040

4141
@abstractmethod
42-
def set_header(self, key: str, value: str):
42+
def set_header(self, key: str, value: str) -> None:
4343
pass
4444

4545
@abstractmethod
46-
def get_header(self, key: str) -> Union[str, None]:
46+
def get_header(self, key: str) -> Optional[str]:
47+
pass
48+
49+
@abstractmethod
50+
def remove_header(self, key: str) -> None:
4751
pass
4852

4953
@abstractmethod

supertokens_python/recipe/session/cookie_and_header.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ def set_header(response: BaseResponse, key: str, value: str, allow_duplicate: bo
9797
response.set_header(key, value)
9898

9999

100+
def remove_header(response: BaseResponse, key: str):
101+
if response.get_header(key) is not None:
102+
response.remove_header(key)
103+
104+
100105
def get_cookie(request: BaseRequest, key: str):
101106
cookie_val = request.get_cookie(key)
102107
if cookie_val is None:
@@ -171,23 +176,31 @@ def get_rid_header(request: BaseRequest):
171176

172177

173178
def clear_session_from_all_token_transfer_methods(
174-
response: BaseResponse, recipe: SessionRecipe, request: BaseRequest
179+
response: BaseResponse, recipe: SessionRecipe
175180
):
181+
# We are clearing the session in all transfermethods to be sure to override cookies in case they have been already added to the response.
182+
# This is done to handle the following use-case:
183+
# If the app overrides signInPOST to check the ban status of the user after the original implementation and throwing an UNAUTHORISED error
184+
# In this case: the SDK has attached cookies to the response, but none was sent with the request
185+
# We can't know which to clear since we can't reliably query or remove the set-cookie header added to the response (causes issues in some frameworks, i.e.: hapi)
186+
# The safe solution in this case is to overwrite all the response cookies/headers with an empty value, which is what we are doing here.
176187
for transfer_method in available_token_transfer_methods:
177-
if get_token(request, "access", transfer_method) is not None:
178-
_clear_session(response, recipe.config, transfer_method)
188+
_clear_session(response, recipe.config, transfer_method)
179189

180190

181191
def _clear_session(
182192
response: BaseResponse,
183193
config: SessionConfig,
184194
transfer_method: TokenTransferMethod,
185195
):
186-
# If we can tell it's a cookie based session we are not clearing using headers
196+
# If we can be specific about which transferMethod we want to clear, there is no reason to clear the other ones
187197
token_types: List[TokenType] = ["access", "refresh"]
188198
for token_type in token_types:
189199
_set_token(response, config, token_type, "", 0, transfer_method)
190200

201+
remove_header(
202+
response, ANTI_CSRF_HEADER_KEY
203+
) # This can be added multiple times in some cases, but that should be OK
191204
set_header(response, FRONT_TOKEN_HEADER_SET_KEY, "remove", False)
192205
set_header(
193206
response, ACCESS_CONTROL_EXPOSE_HEADERS, FRONT_TOKEN_HEADER_SET_KEY, True

supertokens_python/recipe/session/interfaces.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
from .utils import SessionConfig, TokenTransferMethod
3535

3636
if TYPE_CHECKING:
37-
from supertokens_python.framework import BaseRequest, BaseResponse
37+
from supertokens_python.framework import BaseRequest
38+
39+
from supertokens_python.framework import BaseResponse
3840

3941

4042
class SessionObj:

supertokens_python/recipe/session/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ async def on_token_theft_detected(
138138
response: BaseResponse,
139139
) -> BaseResponse:
140140
log_debug_message("Clearing tokens because of TOKEN_THEFT_DETECTED response")
141-
clear_session_from_all_token_transfer_methods(response, recipe, request)
141+
clear_session_from_all_token_transfer_methods(response, recipe)
142142
return await resolve(
143143
self.__on_token_theft_detected(request, session_handle, user_id, response)
144144
)
@@ -159,7 +159,7 @@ async def on_unauthorised(
159159
):
160160
if do_clear_cookies:
161161
log_debug_message("Clearing tokens because of UNAUTHORISED response")
162-
clear_session_from_all_token_transfer_methods(response, recipe, request)
162+
clear_session_from_all_token_transfer_methods(response, recipe)
163163
return await resolve(self.__on_unauthorised(request, message, response))
164164

165165
async def on_invalid_claim(

tests/Django/test_django.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
# under the License.
1414

1515
import json
16+
from datetime import datetime
1617
from inspect import isawaitable
1718
from typing import Any, Dict, Union
1819

19-
from datetime import datetime
2020
from django.http import HttpRequest, HttpResponse, JsonResponse
2121
from django.test import RequestFactory, TestCase
2222
from supertokens_python import InputAppInfo, SupertokensConfig, init
2323
from supertokens_python.framework.django import middleware
24+
from supertokens_python.framework.django.django_response import (
25+
DjangoResponse as SuperTokensDjangoWrapper,
26+
)
2427
from supertokens_python.recipe import emailpassword, session
2528
from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions
2629
from supertokens_python.recipe.session import SessionContainer
@@ -389,3 +392,13 @@ async def test_optional_session(self):
389392
assert response.status_code == 200
390393
dict_response = json.loads(response.content)
391394
assert dict_response["s"] == "empty session"
395+
396+
397+
def test_remove_header_works():
398+
response = HttpResponse()
399+
st_response = SuperTokensDjangoWrapper(response)
400+
401+
st_response.set_header("foo", "bar")
402+
assert st_response.get_header("foo") == "bar"
403+
st_response.remove_header("foo")
404+
assert st_response.get_header("foo") is None

0 commit comments

Comments
 (0)