-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: add CSRF protection middleware #3332
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
cc101f8
2851b30
f1d4330
74b2efe
e2066f6
f1dbc0a
0fe954e
19bddee
6c10c27
d8bf701
708208f
99eeaa5
81b6eb0
b923bfb
ad5eefa
0e211cf
78d3c2a
8513e11
049784b
1c5d5ba
83ffce0
21d065c
cf6c1a2
5951697
86528b5
73e7850
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,8 +10,10 @@ | |||||||||||||||||||||||||
| from aiohttp_security import setup as setup_security | ||||||||||||||||||||||||||
| from aiohttp_security.abc import AbstractAuthorizationPolicy | ||||||||||||||||||||||||||
| from aiohttp_session import setup as setup_session | ||||||||||||||||||||||||||
| from aiohttp_session import get_session | ||||||||||||||||||||||||||
| from aiohttp_session.cookie_storage import EncryptedCookieStorage | ||||||||||||||||||||||||||
| from cryptography import fernet | ||||||||||||||||||||||||||
| import secrets | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| from app.service.interfaces.i_auth_svc import AuthServiceInterface | ||||||||||||||||||||||||||
| from app.service.interfaces.i_login_handler import LoginHandlerInterface | ||||||||||||||||||||||||||
|
|
@@ -75,7 +77,14 @@ async def apply(self, app, users): | |||||||||||||||||||||||||
| app.user_map = self.user_map | ||||||||||||||||||||||||||
| fernet_key = fernet.Fernet.generate_key() | ||||||||||||||||||||||||||
| secret_key = base64.urlsafe_b64decode(fernet_key) | ||||||||||||||||||||||||||
| storage = EncryptedCookieStorage(secret_key, cookie_name=COOKIE_SESSION) | ||||||||||||||||||||||||||
| storage = EncryptedCookieStorage( | ||||||||||||||||||||||||||
| secret_key, | ||||||||||||||||||||||||||
| cookie_name=COOKIE_SESSION, | ||||||||||||||||||||||||||
| secure=True, | ||||||||||||||||||||||||||
| httponly=True, | ||||||||||||||||||||||||||
| max_age=86400, | ||||||||||||||||||||||||||
| samesite='Strict' | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| setup_session(app, storage) | ||||||||||||||||||||||||||
| policy = SessionIdentityPolicy() | ||||||||||||||||||||||||||
| setup_security(app, policy, DictionaryAuthorizationPolicy(self.user_map)) | ||||||||||||||||||||||||||
|
|
@@ -155,6 +164,19 @@ async def handle_successful_login(self, request, username): | |||||||||||||||||||||||||
| self.log.debug('%s logging in', username) | ||||||||||||||||||||||||||
| response = web.HTTPFound('/') | ||||||||||||||||||||||||||
| await remember(request, response, username) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Initialize per-session CSRF token and expose it via a readable cookie for double-submit | ||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| session = await get_session(request) | ||||||||||||||||||||||||||
| if 'csrf_token' not in session: | ||||||||||||||||||||||||||
| session['csrf_token'] = secrets.token_urlsafe(32) | ||||||||||||||||||||||||||
| # Set a non-HttpOnly cookie so client-side JS can read the token for double-submit. | ||||||||||||||||||||||||||
| secure_flag = (request.scheme == 'https') if hasattr(request, 'scheme') else False | ||||||||||||||||||||||||||
| response.set_cookie('XSRF-TOKEN', session['csrf_token'], httponly=False, secure=secure_flag, samesite='Lax') | ||||||||||||||||||||||||||
|
Comment on lines
+174
to
+175
|
||||||||||||||||||||||||||
| secure_flag = (request.scheme == 'https') if hasattr(request, 'scheme') else False | |
| response.set_cookie('XSRF-TOKEN', session['csrf_token'], httponly=False, secure=secure_flag, samesite='Lax') | |
| # Align cookie attributes (max_age, path, secure) with the session cookie settings. | |
| response.set_cookie( | |
| 'XSRF-TOKEN', | |
| session['csrf_token'], | |
| max_age=86400, | |
| path='/', | |
| httponly=False, | |
| secure=True, | |
| samesite='Lax', | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CSRF checks are skipped whenever a
KEYheader is present, even if the API key is invalid. For a session-authenticated request, an attacker could add anyKEYheader value and bypass CSRF validation. Useauth_svc.request_has_valid_api_key(request)(and ideally only skip when API-key auth is the actual auth mechanism) rather than checking header presence.