google / gae-secure-scaffold-python3

Secure scaffold for Google App Engine static and dynamic Python websites
Apache License 2.0
31 stars 16 forks source link

Document how to use CSRF with SPAs #20

Open max-vogler opened 1 year ago

max-vogler commented 1 year ago

Please document how to use this secure scaffold with single page applications, e.g. Angular and React SPAs. These would typically serve the static HTML directly from AppEngine (not through Python templating), which makes it impossible to inject the CSRF token. To make things more complicated, the CSRF token can also not be read from client-side JavaScript, because the secure scaffold defaults set the cookie to HttpOnly.

As far as I can tell, setting the cookie to HttpOnly does not add to the protection in a major way - e.g. see https://docs.djangoproject.com/en/3.0/ref/settings/#csrf-cookie-httponly.

davidwtbuxton commented 1 year ago

Hi, thanks for opening this issue. I will look into updating docs, I would guess there is a relatively straightforward way to change the cookie security policy.

As well as this use case of submitting a CSRF-protected form where the CSRF token isn't accessible from Angular, were there other things blocking you?

max-vogler commented 1 year ago

Yes, straightforward workarounds are:

  1. Change the settings.py file and set CSRF_COOKIE_HTTPONLY=False or
  2. app.csrf._csrf_httponly = False after app = securescaffold.create_app(__name__)

Preferably, there would be a documented way to do this, that doesn't require changing the secure defaults of this repository.

Other issues were:

DEFAULT 2023-05-15T07:12:37.920842Z File "/workspace/app.py", line 31, in <module>
DEFAULT 2023-05-15T07:12:37.920855Z app = securescaffold.create_app(__name__)
DEFAULT 2023-05-15T07:12:37.920862Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920870Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/securescaffold/factory.py", line 63, in create_app
DEFAULT 2023-05-15T07:12:37.920877Z configure_app(app)
DEFAULT 2023-05-15T07:12:37.920885Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/securescaffold/factory.py", line 91, in configure_app
DEFAULT 2023-05-15T07:12:37.920893Z config = get_config_from_datastore()
DEFAULT 2023-05-15T07:12:37.920900Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920909Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/securescaffold/factory.py", line 100, in get_config_from_datastore
DEFAULT 2023-05-15T07:12:37.920916Z obj = AppConfig.singleton()
DEFAULT 2023-05-15T07:12:37.920923Z ^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920932Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/securescaffold/factory.py", line 42, in singleton
DEFAULT 2023-05-15T07:12:37.920938Z obj = cls.get_or_insert(cls.SINGLETON_ID, **config)
DEFAULT 2023-05-15T07:12:37.920945Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920953Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/_options.py", line 102, in wrapper
DEFAULT 2023-05-15T07:12:37.920962Z return wrapped(*pass_args, **kwargs)
DEFAULT 2023-05-15T07:12:37.920969Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920977Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/utils.py", line 150, in positional_wrapper
DEFAULT 2023-05-15T07:12:37.920983Z return wrapped(*args, **kwds)
DEFAULT 2023-05-15T07:12:37.920989Z ^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.920996Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/model.py", line 5930, in _get_or_insert
DEFAULT 2023-05-15T07:12:37.921004Z return _cls._get_or_insert_async(_name, *args, **kwargs).result()
DEFAULT 2023-05-15T07:12:37.921012Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921021Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 210, in result
DEFAULT 2023-05-15T07:12:37.921028Z self.check_success()
DEFAULT 2023-05-15T07:12:37.921036Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 157, in check_success
DEFAULT 2023-05-15T07:12:37.921044Z raise self._exception
DEFAULT 2023-05-15T07:12:37.921053Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 319, in _advance_tasklet
DEFAULT 2023-05-15T07:12:37.921060Z yielded = self.generator.throw(type(error), error, traceback)
DEFAULT 2023-05-15T07:12:37.921068Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921076Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/model.py", line 6033, in get_or_insert
DEFAULT 2023-05-15T07:12:37.921084Z entity = yield key.get_async(_options=options)
DEFAULT 2023-05-15T07:12:37.921091Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921101Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 319, in _advance_tasklet
DEFAULT 2023-05-15T07:12:37.921110Z yielded = self.generator.throw(type(error), error, traceback)
DEFAULT 2023-05-15T07:12:37.921118Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921125Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/key.py", line 894, in get
DEFAULT 2023-05-15T07:12:37.921132Z entity_pb = yield _datastore_api.lookup(self._key, _options)
DEFAULT 2023-05-15T07:12:37.921150Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921168Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 319, in _advance_tasklet
DEFAULT 2023-05-15T07:12:37.921174Z yielded = self.generator.throw(type(error), error, traceback)
DEFAULT 2023-05-15T07:12:37.921182Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921190Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/_datastore_api.py", line 162, in lookup
DEFAULT 2023-05-15T07:12:37.921196Z entity_pb = yield batch.add(key)
DEFAULT 2023-05-15T07:12:37.921203Z ^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921210Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 319, in _advance_tasklet
DEFAULT 2023-05-15T07:12:37.921218Z yielded = self.generator.throw(type(error), error, traceback)
DEFAULT 2023-05-15T07:12:37.921223Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921232Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/_retry.py", line 96, in retry_wrapper
DEFAULT 2023-05-15T07:12:37.921239Z raise error
DEFAULT 2023-05-15T07:12:37.921248Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/_retry.py", line 82, in retry_wrapper
DEFAULT 2023-05-15T07:12:37.921257Z result = yield result
DEFAULT 2023-05-15T07:12:37.921266Z ^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921275Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/tasklets.py", line 319, in _advance_tasklet
DEFAULT 2023-05-15T07:12:37.921282Z yielded = self.generator.throw(type(error), error, traceback)
DEFAULT 2023-05-15T07:12:37.921290Z ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEFAULT 2023-05-15T07:12:37.921297Z File "/layers/google.python.pip/pip/lib/python3.11/site-packages/google/cloud/ndb/_datastore_api.py", line 99, in rpc_call
DEFAULT 2023-05-15T07:12:37.921305Z raise error
DEFAULT 2023-05-15T07:12:37.921315Z google.api_core.exceptions.PermissionDenied: 403 Missing or insufficient permissions.