Skip to main content

A session implementation for Flask using DynamoDB as a backing store and OWASP best practices for session management.

Project description

dynamodb-session-flask

An implementation of a Flask session using DynamoDB as backend storage. This project was built on dynamodb-session-web, but with support for the Flask framework.

Why This Library?

I tried and acquired an appreciation for some other DynamoDB backend implementations for Flask sessions. However, I needed a few extra things:

  • Absolute and Idle Timeouts
  • Support for using a header (not a cookie) for session ID

In addition to the OWASP Session Management best practices implemented in dynamodb-session-web, this project has additional support for these best practices:

  • Non-descript session ID name - Defaults to id for cookies, and x-id for headers.
    • Side-Comment - isn't a non-descript suggestion for a name actually descriptive?
  • Cookie setting defaults:
    • Secure = True
    • HttpOnly = True
    • SameSite = Strict
    • Domain and Path - Must set these yourself
  • ID Exchange
    • Accepted session ID mechanism (i.e. cookie vs header) is enforced. That is, user cannot submit session IDs through a header if cookie is expected.

Usage

Requires a DynamoDB table named app_session (can be changed in settings).

Here's an example table creation statement:

aws dynamodb create-table \
    --attribute-definitions \
        AttributeName=id,AttributeType=S \
    --key-schema "AttributeName=id,KeyType=HASH" \
    --provisioned-throughput "ReadCapacityUnits=5,WriteCapacityUnits=5" \
    --table-name app_session 

Sessions are intended to operate just like the default Flask session implementation:

from flask import Flask, session
from dynamodb_session_flask import DynamoDbSession

flask_app = Flask(__name__)
flask_app.session_interface = DynamoDbSession()

@flask_app.route('/save')
def save():
    session['val'] = 'My Value'
    return 'Success', 200

@flask_app.route('/load')
def load():
    saved_val = session['val']
    return saved_val, 200

@flask_app.route('/end')
def end_session():
    # This will remove the session from the database and remove the session ID from cookies/headers
    session.clear()
    return 'Success', 200

If using the extra methods that are provided ([see below](#Session Instance Methods)), you may find it useful to have an extra module-level variable. It helps with IDE code completion.

from typing import cast

from flask import Flask, session as flask_session
from dynamodb_session_flask import DynamoDbSessionInstance

dynamodb_session = cast(DynamoDbSessionInstance, flask_session)


def abandon_session():
    dynamodb_session.abandon()

Behavior

Works within the Flask session interface:

  • Presents a dictionary-like interface for getting/setting values.
  • Session is loaded at the start of the request, and saved at the end.

Additional behaviors:

  • Session is not saved and ID is not returned if the session is new and no data is added.

Session Instance Methods

While this session implementation is backwards compatible with the Flask session functionality/interface, there are some additional methods available that can be used if needed.

abandon()

Immediately removes the session from the database.

create()

Creates a new session, with new ID. Does not remove the old session record.

save()

Manually saves the session.

This method is not usually needed since Flask will save the session at the end of a request. However, it is provided for cases where the session must be saved earlier.

Configuration

There are additional configuration options, and are set like normal Flask configuration:

flask_app = Flask(__name__)
flask_app.config.update(
    SESSION_DYNAMODB_IDLE_TIMEOUT=600
)

All configuration is optional, assuming the defaults are okay.

SESSION_DYNAMODB_ABSOLUTE_TIMEOUT

Absolute session timeout (in seconds).

Note: This setting works in conjunction with Flask's PERMANENT_SESSION_LIFETIME setting. The absolute timeout chosen will be whichever is less.

Default: 43200 (12 hours)

SESSION_DYNAMODB_ENDPOINT_URL

The DynamoDB URL.

Default: None (i.e. Boto3 logic)

SESSION_DYNAMODB_HEADER_NAME

The name of the header to use for the session ID.

Default: x-id

SESSION_DYNAMODB_IDLE_TIMEOUT

Idle session timeout (in seconds).

Default: 7200 (2 hours)

SESSION_DYNAMODB_SID_BYTE_LENGTH

Session ID length in bytes.

This does not correlate to the character length of the ID, which will be either:

  • 43 - How many characters a 32-byte value uses when Base64 encoded.
  • 71 - The 43 characters from the previous bullet, plus a dot and finally a 27-character HMAC signature.

Default: 32

SESSION_DYNAMODB_SID_KEYS

For a slightly more secure session ID, the key can be signed using a configurable and rotatable key.

The signature is generated using itsdangerous and includes key rotation. If/When rotation is desired, the array is used in order from oldest to newest. Otherwise, one key is all that is needed.

An empty array means no signature is generated.

Default: [] (no signature)

SESSION_DYNAMODB_TABLE_NAME

The name of the DynamoDB table.

Default: app_session

SESSION_DYNAMODB_OVERRIDE_COOKIE_NAME

Whether or not to override Flask's [SESSION_COOKIE_NAME](https://flask.palletsprojects.com/en/2.0.x/config/#SESSION_COOKIE_NAME) configuration for the session ID. While somewhat trivial, OWASP's recommended value is `id` and Flask's default is `session`. So to avoid using Flask's default or modifying it behind the scenes, this setting helps separate this library's preferred default from Flask's.

Setting this to True will set the cookie name to id. Otherwise, Flask's configuration will be used.

Default: True

SESSION_DYNAMODB_OVERRIDE_COOKIE_SECURE

Whether or not to override Flask's [`SESSION_COOKIE_SECURE`](https://flask.palletsprojects.com/en/2.0.x/config/#SESSION_COOKIE_SECURE) for the cookie's Secure attribute. Flask defaults that attribute to `False`, whereas this should ideally be `True` to prevent Man-in-the-Middle attacks.

Setting this to True will force the Secure attribute to also be True. Otherwise, Flask's configuration will be used.

Note: You'll want to set this to False in any environment where TLS is not used (e.g. local development).

Default: True

SESSION_DYNAMODB_USE_HEADER

Whether or not to check for the session ID via headers. The cookie value is still used by default (if found).

Default: False

SESSION_COOKIE_SAMESITE

This is actually a Flask configuration, which defaults to `None`. However, if the value is `None`, then we set it to `Strict` by default.

Default: Strict (indirectly changed)

Testing

Flask has a pattern for accessing the session when running tests. This mechanism still uses the backend session_interface set for the app (i.e. it will still use DynamoDB).

To help reduce dependencies when simply trying to run unit tests that need a value set in the session, there's a separate session_interface that can be used.

Below is a working example, copied from this project's tests. Improvements could be made depending on test expectations.

import pytest
from dynamodb_session_flask.testing import TestSession
from flask import Flask, session


@pytest.fixture
def app():
    flask_app = Flask(__name__)

    @flask_app.route('/load')
    def load():
        return {
            'actual_value': session.get('val', None),
        }

    yield flask_app


@pytest.fixture()
def test_client(app):
    app.session_interface = TestSession()
    return app.test_client()


def test_able_to_use_test_session_transaction(test_client):
    expected_value = 'fake_value'

    with test_client:
        with test_client.session_transaction() as test_session:
            test_session['val'] = expected_value

        response = test_client.get('/load')

        assert response.json['actual_value'] == expected_value

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dynamodb-session-flask-1.1.3.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

dynamodb_session_flask-1.1.3-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

Details for the file dynamodb-session-flask-1.1.3.tar.gz.

File metadata

  • Download URL: dynamodb-session-flask-1.1.3.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.2.0 CPython/3.9.13 Darwin/21.6.0

File hashes

Hashes for dynamodb-session-flask-1.1.3.tar.gz
Algorithm Hash digest
SHA256 f11b0c67924a759269cdc7acc89f2d41c19cd4fc11ca4e9e6bfad84551cd96ad
MD5 b3b5a53e8eb4ec92d75ae5c6ddd0fc3d
BLAKE2b-256 c9ccd602ff7c8083da528641e2c2c08c9b76cf701f31e52b08425bfc2b690133

See more details on using hashes here.

File details

Details for the file dynamodb_session_flask-1.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for dynamodb_session_flask-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d75d241780c1c53ca0629a0ca6f9e0a0180b703e615165830a772ec00712e944
MD5 d28b5ea7da3cf01d63bf8bef78e02026
BLAKE2b-256 e1b776e0326fc7599d1c3f6096638b462edec010a0ac2ba98f47e1f45f3c3f5c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page