Package: dynamodb-session-flask
dynamodb-session-flaskAn implementation of a Flask session using DynamoDB as backend storage. This project was built ondynamodb-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 TimeoutsSupport for using a header (not a cookie) for session IDIn addition to theOWASP Session Managementbest practices implemented indynamodb-session-web,
this project has additional support for these best practices:Non-descript session ID name - Defaults toidfor cookies, andx-idfor headers.Side-Comment - isn't a non-descript suggestion for a name actually descriptive?Cookie setting defaults:Secure = TrueHttpOnly = TrueSameSite = StrictDomain and Path - Must set these yourselfID ExchangeAccepted session ID mechanism (i.e. cookie vs header) is enforced. That is, user cannot submit session IDs
through a header if cookie is expected.UsageRequires a DynamoDB table namedapp_session(can be changed in settings).Here's an example table creation statement:awsdynamodbcreate-table\--attribute-definitions\AttributeName=id,AttributeType=S\--key-schema"AttributeName=id,KeyType=HASH"\--provisioned-throughput"ReadCapacityUnits=5,WriteCapacityUnits=5"\--table-nameapp_sessionSessions are intended to operate just like the default Flask session implementation:fromflaskimportFlask,sessionfromdynamodb_session_flaskimportDynamoDbSessionflask_app=Flask(__name__)flask_app.session_interface=DynamoDbSession()@flask_app.route('/save')defsave():session['val']='My Value'return'Success',200@flask_app.route('/load')defload():saved_val=session['val']returnsaved_val,200@flask_app.route('/end')defend_session():# This will remove the session from the database and remove the session ID from cookies/headerssession.clear()return'Success',200If 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.fromtypingimportcastfromflaskimportFlask,sessionasflask_sessionfromdynamodb_session_flaskimportDynamoDbSessionInstancedynamodb_session=cast(DynamoDbSessionInstance,flask_session)defabandon_session():dynamodb_session.abandon()BehaviorWorks 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 MethodsWhile 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.ConfigurationThere 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_TIMEOUTAbsolute session timeout (in seconds).Note: This setting works in conjunction with Flask'sPERMANENT_SESSION_LIFETIMEsetting.  The absolute timeout chosen will be whichever is less.Default:43200(12 hours)SESSION_DYNAMODB_ENDPOINT_URLThe DynamoDB URL.Default:None(i.e. Boto3 logic)SESSION_DYNAMODB_HEADER_NAMEThe name of the header to use for the session ID.Default:x-idSESSION_DYNAMODB_IDLE_TIMEOUTIdle session timeout (in seconds).Default:7200(2 hours)SESSION_DYNAMODB_SID_BYTE_LENGTHSession 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:32SESSION_DYNAMODB_SID_KEYSFor a slightly more secure session ID, the key can be signed using a configurable and rotatable key.The signature is generated usingitsdangerousand 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_NAMEThe name of the DynamoDB table.Default:app_sessionSESSION_DYNAMODB_OVERRIDE_COOKIE_NAMEWhether 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 toTruewill set the cookie name toid. Otherwise, Flask's configuration will be used.Default:TrueSESSION_DYNAMODB_OVERRIDE_COOKIE_SECUREWhether 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 toTruewill force the Secure attribute to also beTrue. Otherwise, Flask's configuration will be used.Note: You'll want to set this toFalsein any environment where TLS is not used (e.g. local development).Default:TrueSESSION_DYNAMODB_USE_HEADERWhether or not to check for the session ID via headers. The cookie value is still used by default (if found).Default:FalseSESSION_COOKIE_SAMESITEThis 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)TestingFlask has apattern for accessing the sessionwhen running tests.
This mechanism still uses the backendsession_interfaceset 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
separatesession_interfacethat can be used.Below is a working example, copied fromthis project's tests. Improvements could be made depending on test expectations.importpytestfromdynamodb_session_flask.testingimportTestSessionfromflaskimportFlask,session@pytest.fixturedefapp():flask_app=Flask(__name__)@flask_app.route('/load')defload():return{'actual_value':session.get('val',None),}yieldflask_app@pytest.fixture()deftest_client(app):app.session_interface=TestSession()returnapp.test_client()deftest_able_to_use_test_session_transaction(test_client):expected_value='fake_value'withtest_client:withtest_client.session_transaction()astest_session:test_session['val']=expected_valueresponse=test_client.get('/load')assertresponse.json['actual_value']==expected_value