Skip to content

Commit b639d39

Browse files
authored
Feat: override cookie domain from cookie utils (#447)
1 parent ed96d32 commit b639d39

File tree

2 files changed

+101
-20
lines changed

2 files changed

+101
-20
lines changed

flask_jwt_extended/utils.py

+39-15
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def get_csrf_token(encoded_token):
259259
return token["csrf"]
260260

261261

262-
def set_access_cookies(response, encoded_access_token, max_age=None):
262+
def set_access_cookies(response, encoded_access_token, max_age=None, domain=None):
263263
"""
264264
Modifiy a Flask Response to set a cookie containing the access JWT.
265265
Also sets the corresponding CSRF cookies if ``JWT_CSRF_IN_COOKIES`` is ``True``
@@ -276,14 +276,20 @@ def set_access_cookies(response, encoded_access_token, max_age=None):
276276
``JWT_SESSION_COOKIE`` option (see :ref:`Configuration Options`). Otherwise,
277277
it will use this as the cookies ``max-age`` and the JWT_SESSION_COOKIE option
278278
will be ignored. Values should be the number of seconds (as an integer).
279+
280+
:param domain:
281+
The domain of the cookie. If this is None, it will use the
282+
``JWT_COOKIE_DOMAIN`` option (see :ref:`Configuration Options`). Otherwise,
283+
it will use this as the cookies ``domain`` and the JWT_COOKIE_DOMAIN option
284+
will be ignored.
279285
"""
280286
response.set_cookie(
281287
config.access_cookie_name,
282288
value=encoded_access_token,
283289
max_age=max_age or config.cookie_max_age,
284290
secure=config.cookie_secure,
285291
httponly=True,
286-
domain=config.cookie_domain,
292+
domain=domain or config.cookie_domain,
287293
path=config.access_cookie_path,
288294
samesite=config.cookie_samesite,
289295
)
@@ -295,13 +301,13 @@ def set_access_cookies(response, encoded_access_token, max_age=None):
295301
max_age=max_age or config.cookie_max_age,
296302
secure=config.cookie_secure,
297303
httponly=False,
298-
domain=config.cookie_domain,
304+
domain=domain or config.cookie_domain,
299305
path=config.access_csrf_cookie_path,
300306
samesite=config.cookie_samesite,
301307
)
302308

303309

304-
def set_refresh_cookies(response, encoded_refresh_token, max_age=None):
310+
def set_refresh_cookies(response, encoded_refresh_token, max_age=None, domain=None):
305311
"""
306312
Modifiy a Flask Response to set a cookie containing the refresh JWT.
307313
Also sets the corresponding CSRF cookies if ``JWT_CSRF_IN_COOKIES`` is ``True``
@@ -318,14 +324,20 @@ def set_refresh_cookies(response, encoded_refresh_token, max_age=None):
318324
``JWT_SESSION_COOKIE`` option (see :ref:`Configuration Options`). Otherwise,
319325
it will use this as the cookies ``max-age`` and the JWT_SESSION_COOKIE option
320326
will be ignored. Values should be the number of seconds (as an integer).
327+
328+
:param domain:
329+
The domain of the cookie. If this is None, it will use the
330+
``JWT_COOKIE_DOMAIN`` option (see :ref:`Configuration Options`). Otherwise,
331+
it will use this as the cookies ``domain`` and the JWT_COOKIE_DOMAIN option
332+
will be ignored.
321333
"""
322334
response.set_cookie(
323335
config.refresh_cookie_name,
324336
value=encoded_refresh_token,
325337
max_age=max_age or config.cookie_max_age,
326338
secure=config.cookie_secure,
327339
httponly=True,
328-
domain=config.cookie_domain,
340+
domain=domain or config.cookie_domain,
329341
path=config.refresh_cookie_path,
330342
samesite=config.cookie_samesite,
331343
)
@@ -337,39 +349,45 @@ def set_refresh_cookies(response, encoded_refresh_token, max_age=None):
337349
max_age=max_age or config.cookie_max_age,
338350
secure=config.cookie_secure,
339351
httponly=False,
340-
domain=config.cookie_domain,
352+
domain=domain or config.cookie_domain,
341353
path=config.refresh_csrf_cookie_path,
342354
samesite=config.cookie_samesite,
343355
)
344356

345357

346-
def unset_jwt_cookies(response):
358+
def unset_jwt_cookies(response, domain=None):
347359
"""
348360
Modifiy a Flask Response to delete the cookies containing access or refresh
349361
JWTs. Also deletes the corresponding CSRF cookies if applicable.
350362
351363
:param response:
352364
A Flask Response object
353365
"""
354-
unset_access_cookies(response)
355-
unset_refresh_cookies(response)
366+
unset_access_cookies(response, domain)
367+
unset_refresh_cookies(response, domain)
356368

357369

358-
def unset_access_cookies(response):
370+
def unset_access_cookies(response, domain=None):
359371
"""
360372
Modifiy a Flask Response to delete the cookie containing a refresh JWT.
361373
Also deletes the corresponding CSRF cookie if applicable.
362374
363375
:param response:
364376
A Flask Response object
377+
378+
:param domain:
379+
The domain of the cookie. If this is None, it will use the
380+
``JWT_COOKIE_DOMAIN`` option (see :ref:`Configuration Options`). Otherwise,
381+
it will use this as the cookies ``domain`` and the JWT_COOKIE_DOMAIN option
382+
will be ignored.
365383
"""
366384
response.set_cookie(
367385
config.access_cookie_name,
368386
value="",
369387
expires=0,
370388
secure=config.cookie_secure,
371389
httponly=True,
372-
domain=config.cookie_domain,
390+
domain=domain or config.cookie_domain,
373391
path=config.access_cookie_path,
374392
samesite=config.cookie_samesite,
375393
)
@@ -381,27 +399,33 @@ def unset_access_cookies(response):
381399
expires=0,
382400
secure=config.cookie_secure,
383401
httponly=False,
384-
domain=config.cookie_domain,
402+
domain=domain or config.cookie_domain,
385403
path=config.access_csrf_cookie_path,
386404
samesite=config.cookie_samesite,
387405
)
388406

389407

390-
def unset_refresh_cookies(response):
408+
def unset_refresh_cookies(response, domain=None):
391409
"""
392410
Modifiy a Flask Response to delete the cookie containing an access JWT.
393411
Also deletes the corresponding CSRF cookie if applicable.
394412
395413
:param response:
396414
A Flask Response object
415+
416+
:param domain:
417+
The domain of the cookie. If this is None, it will use the
418+
``JWT_COOKIE_DOMAIN`` option (see :ref:`Configuration Options`). Otherwise,
419+
it will use this as the cookies ``domain`` and the JWT_COOKIE_DOMAIN option
420+
will be ignored.
397421
"""
398422
response.set_cookie(
399423
config.refresh_cookie_name,
400424
value="",
401425
expires=0,
402426
secure=config.cookie_secure,
403427
httponly=True,
404-
domain=config.cookie_domain,
428+
domain=domain or config.cookie_domain,
405429
path=config.refresh_cookie_path,
406430
samesite=config.cookie_samesite,
407431
)
@@ -413,7 +437,7 @@ def unset_refresh_cookies(response):
413437
expires=0,
414438
secure=config.cookie_secure,
415439
httponly=False,
416-
domain=config.cookie_domain,
440+
domain=domain or config.cookie_domain,
417441
path=config.refresh_csrf_cookie_path,
418442
samesite=config.cookie_samesite,
419443
)

tests/test_cookies.py

+62-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from flask import Flask
33
from flask import jsonify
4+
from flask import request
45

56
from flask_jwt_extended import create_access_token
67
from flask_jwt_extended import create_refresh_token
@@ -35,34 +36,39 @@ def app():
3536

3637
@app.route("/access_token", methods=["GET"])
3738
def access_token():
39+
domain = request.args.get("domain")
3840
resp = jsonify(login=True)
3941
access_token = create_access_token("username")
40-
set_access_cookies(resp, access_token)
42+
set_access_cookies(resp, access_token, domain=domain)
4143
return resp
4244

4345
@app.route("/refresh_token", methods=["GET"])
4446
def refresh_token():
47+
domain = request.args.get("domain")
4548
resp = jsonify(login=True)
4649
refresh_token = create_refresh_token("username")
47-
set_refresh_cookies(resp, refresh_token)
50+
set_refresh_cookies(resp, refresh_token, domain=domain)
4851
return resp
4952

5053
@app.route("/delete_tokens", methods=["GET"])
5154
def delete_tokens():
55+
domain = request.args.get("domain")
5256
resp = jsonify(logout=True)
53-
unset_jwt_cookies(resp)
57+
unset_jwt_cookies(resp, domain=domain)
5458
return resp
5559

5660
@app.route("/delete_access_tokens", methods=["GET"])
5761
def delete_access_tokens():
62+
domain = request.args.get("domain")
5863
resp = jsonify(access_revoked=True)
59-
unset_access_cookies(resp)
64+
unset_access_cookies(resp, domain=domain)
6065
return resp
6166

6267
@app.route("/delete_refresh_tokens", methods=["GET"])
6368
def delete_refresh_tokens():
69+
domain = request.args.get("domain")
6470
resp = jsonify(refresh_revoked=True)
65-
unset_refresh_cookies(resp)
71+
unset_refresh_cookies(resp, domain=domain)
6672
return resp
6773

6874
@app.route("/protected", methods=["GET"])
@@ -494,3 +500,54 @@ def test_jwt_optional_with_csrf_enabled(app):
494500
response = test_client.post("/optional_post_protected")
495501
assert response.status_code == 401
496502
assert response.get_json() == {"msg": "Missing CSRF token"}
503+
504+
505+
@pytest.mark.parametrize(
506+
"options",
507+
[
508+
(
509+
"/access_token",
510+
"/delete_access_tokens",
511+
"access_token_cookie",
512+
"csrf_access_token",
513+
),
514+
(
515+
"/refresh_token",
516+
"/delete_refresh_tokens",
517+
"refresh_token_cookie",
518+
"csrf_refresh_token",
519+
),
520+
],
521+
)
522+
def test_override_domain_option(app, options):
523+
auth_url, delete_url, auth_cookie_name, csrf_cookie_name = options
524+
domain = "yolo.com"
525+
526+
test_client = app.test_client()
527+
app.config["JWT_COOKIE_DOMAIN"] = "test.com"
528+
529+
# Test set access cookies with custom domain
530+
response = test_client.get(f"{auth_url}?domain={domain}")
531+
cookies = response.headers.getlist("Set-Cookie")
532+
assert len(cookies) == 2 # JWT and CSRF value
533+
534+
access_cookie = _get_cookie_from_response(response, auth_cookie_name)
535+
assert access_cookie is not None
536+
assert access_cookie["domain"] == domain
537+
538+
access_csrf_cookie = _get_cookie_from_response(response, csrf_cookie_name)
539+
assert access_csrf_cookie is not None
540+
assert access_csrf_cookie["domain"] == domain
541+
542+
# Test unset access cookies with custom domain
543+
response = test_client.get(f"{delete_url}?domain={domain}")
544+
cookies = response.headers.getlist("Set-Cookie")
545+
assert len(cookies) == 2 # JWT and CSRF value
546+
547+
access_cookie = _get_cookie_from_response(response, auth_cookie_name)
548+
assert access_cookie is not None
549+
assert access_cookie["domain"] == domain
550+
551+
access_csrf_cookie = _get_cookie_from_response(response, csrf_cookie_name)
552+
assert access_csrf_cookie is not None
553+
assert access_csrf_cookie["domain"] == domain

0 commit comments

Comments
 (0)