Skip to content

Commit

Permalink
Merge pull request #31 from mblackgeo/feat/add-cookie-domain
Browse files Browse the repository at this point in the history
feat/add cookie domain
  • Loading branch information
mblackgeo authored Oct 30, 2023
2 parents 8babbfc + 90fd6ac commit 9f92aa6
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 15 deletions.
28 changes: 15 additions & 13 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@

The following key/value pairs are used for configurating the extension:

| **Config Name** | **Description** |
|---------------------------------------|------------------------------------------------------------------------------------------|
| `AWS_COGNITO_DISABLED` | Globally disable auth with Cognito (default=False) |
| `AWS_REGION` | Region the user pool was created |
| `AWS_COGNITO_DOMAIN` | The domain name of the user pool |
| `AWS_COGNITO_USER_POOL_ID` | The ID of the user pool |
| `AWS_COGNITO_USER_POOL_CLIENT_ID` | The user pool app client ID (*) |
| `AWS_COGNITO_USER_POOL_CLIENT_SECRET` | The user pool app client secret (*) [Optional for public Cognito clients] |
| `AWS_COGNITO_REDIRECT_URL` | The full URL of the route that handles post-login flow |
| `AWS_COGNITO_LOGOUT_URL` | The full URL of the route that handles post-logout flow |
| `AWS_COGNITO_COOKIE_AGE_SECONDS` | (Optional) How long to store the access token cookie. (default=1800) |
| `AWS_COGNITO_EXPIRATION_LEEWAY` | (Optional) Leeway (in seconds) when checking for token expiry (default=0) |
| `AWS_COGNITO_SCOPES` | (Optional) List of scopes to request from Cognito, if None (default) will get all scopes |
| **Config Name** | **Description** |
|---------------------------------------|-------------------------------------------------------------------------------------------|
| `AWS_COGNITO_DISABLED` | Globally disable auth with Cognito (default=False) |
| `AWS_REGION` | Region the user pool was created |
| `AWS_COGNITO_DOMAIN` | The domain name of the user pool |
| `AWS_COGNITO_USER_POOL_ID` | The ID of the user pool |
| `AWS_COGNITO_USER_POOL_CLIENT_ID` | The user pool app client ID (*) |
| `AWS_COGNITO_USER_POOL_CLIENT_SECRET` | The user pool app client secret (*) [Optional for public Cognito clients] |
| `AWS_COGNITO_REDIRECT_URL` | The full URL of the route that handles post-login flow |
| `AWS_COGNITO_LOGOUT_URL` | The full URL of the route that handles post-logout flow |
| `AWS_COGNITO_COOKIE_AGE_SECONDS` | (Optional) How long to store the access token cookie. (default=1800) |
| `AWS_COGNITO_EXPIRATION_LEEWAY` | (Optional) Leeway (in seconds) when checking for token expiry (default=0) |
| `AWS_COGNITO_SCOPES` | (Optional) List of scopes to request from Cognito, if None (default) will get all scopes |
| `AWS_COGNITO_COOKIE_DOMAIN` | (Optional) Domain used for setting a cookie (default=None) |
| `AWS_COGNITO_COOKIE_SAMESITE` | (Optional) Setting for "samesite" on the cookie. Choose "lax", "strict" or None (default) |

(*) To obtain these values, navigate to the user pool in the AWS Cognito console, then head to the "App Integration" tab. Under the app client list, select the app client and you should be able to view the Client ID and Client Secret

Expand Down
27 changes: 26 additions & 1 deletion src/flask_cognito_lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,32 @@ def cognito_scopes(self) -> Optional[List[str]]:
Return the scopes to request from Cognito.
If None, all supported scopes are returned
"""
return get("AWS_COGNITO_SCOPES", required=False, default=None)
return get("AWS_COGNITO_SCOPES", required=False)

@property
def cookie_domain(self) -> str:
"""Return the domain used for the cookie.
Used if you want to set a cross-domain cookie.
For example, domain=".example.com" will set a cookie that is readable
by the domain www.example.com, foo.example.com etc.
If not set (default) then the cookie will only be readable by the
domain that set it.
"""
return get("AWS_COGNITO_COOKIE_DOMAIN", required=False)

@property
def cookie_samesite(self) -> str:
"""Return the property to set for "samesite" on the cookie
The SameSite attribute lets servers specify whether/when cookies are
sent with cross-site requests (where Site is defined by the registrable
domain and the scheme: http or https). This provides some protection
against cross-site request forgery attacks (CSRF).
It takes three possible values: Strict, Lax, and None.
"""
return get("AWS_COGNITO_COOKIE_SAMESITE", required=False)

@property
def issuer(self) -> str:
Expand Down
4 changes: 3 additions & 1 deletion src/flask_cognito_lib/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def wrapper(*args, **kwargs):
max_age=cfg.max_cookie_age_seconds,
httponly=True,
secure=True,
samesite=cfg.cookie_samesite,
domain=cfg.cookie_domain,
)

return resp
Expand All @@ -140,7 +142,7 @@ def wrapper(*args, **kwargs):
with app.app_context():
# logout at cognito and remove the cookies
resp = redirect(cfg.logout_endpoint)
resp.delete_cookie(key=cfg.COOKIE_NAME)
resp.delete_cookie(key=cfg.COOKIE_NAME, domain=cfg.cookie_domain)

# Cognito will redirect to the sign-out URL (if set) or else use
# the callback URL
Expand Down
40 changes: 40 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,46 @@ def test_cognito_login_callback(client, cfg, access_token, token_response):
assert "user_info" in session


def test_cognito_login_cookie_domain(client, cfg, access_token, token_response):
# set a domain for the cookie
client.application.config["AWS_COGNITO_COOKIE_DOMAIN"] = ".example.com"

with client as c:
with c.session_transaction() as sess:
sess["code_verifier"] = "1234"
sess["state"] = "5678"
sess["nonce"] = "MSln6nvPIIBVMhsNUOtUCtssceUKz4dhCRZi5QZRU4A="

# returns OK and sets the cookie
response = client.get("/postlogin")
assert response.status_code == 200
assert response.data.decode("utf-8") == "ok"

# check that the cookie is being set with the correct domain configuration
assert "Domain=example.com" in response.headers["Set-Cookie"]


def test_cognito_login_cookie_samesite(client, cfg, access_token, token_response):
# set a domain for the cookie
client.application.config["AWS_COGNITO_COOKIE_DOMAIN"] = ".example.com"
client.application.config["AWS_COGNITO_COOKIE_SAMESITE"] = "Strict"

with client as c:
with c.session_transaction() as sess:
sess["code_verifier"] = "1234"
sess["state"] = "5678"
sess["nonce"] = "MSln6nvPIIBVMhsNUOtUCtssceUKz4dhCRZi5QZRU4A="

# returns OK and sets the cookie
response = client.get("/postlogin")
assert response.status_code == 200
assert response.data.decode("utf-8") == "ok"

# check that the cookie is being set with the correct domain configuration
assert "Domain=example.com" in response.headers["Set-Cookie"]
assert "SameSite=Strict" in response.headers["Set-Cookie"]


def test_cognito_logout(client, cfg):
# should 302 redirect to cognito
response = client.get("/logout")
Expand Down

0 comments on commit 9f92aa6

Please sign in to comment.