After we successfully deploy a PHP application in cluster environment, we still need some efforts to make the application to be stateless. One of them is building centralized session management. In this post, I will demonstrate how we can provision an Amazon DynamoDB table using AWS CDK and how to configure our application to securely use the created table for session storage. I will also demonstrate how to set up the local environment to test the session using Docker compose and Localstack.

This post is part of series Building Modern PHP/Yii2 Application using AWS (or see here for Indonesian version).

By default, PHP will store its session into local directory, usually in /tmp for Linux distributions. Since we are using load balanced multiple PHP applications, this can cause problem. We cannot really guarantee a single user will access the same application that will access the same file. Yes, we can set the Application Load Balancer to use Sticky Session so the load balancer can use cookie to target same instance for the same user. But consider when container stops because autoscaling suddenly scale down or case of the container fails. This will log users out who are still using that instance and cause bad experience.

Sessions in Multiple App

To solve this, we are going to use DynamoDB as a centralized session storage. Since DynamoDB is a fully managed key-value database and it can delivers within single-digit miliseconds, it is a fitting solution for this challenge.

Using Session in Yii2

We are going to pick up from previous article where we create CI/CD Pipeline. You can access the code here.

In order to use session feature in Yii2, we will use predefined session from Yii application components. We can set a value into the component and the value will persist during the user’s sessions.

The following code shows a controller where we are going to demonstrate using sessions.

https://gist.github.com/petrabarus/22ad66f6a0768ace91335bc58eb439f3#file-testcontroller-php

Controller TestController will be able to receive query parameter name. If we pass a value with the query parameter name, it will save into the session. Otherwise it will get the previously set sessions. The controller will output the value stored in the session and the ID of the session.

After that we will enable routing so that we can access the controller through path /test/index. Add the following configuration to override the default urlManager component..

//file: /config/web.php
//put this in the `components` list of values.
//    'components' => [
//      .....
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],

We can try deploying the new code using CDK command deploy. (I changed the stack name to MyWebStack to differentiate between the stack in the previous article).

cdk deploy MyWebStack

You can test by accessing URL <URL>/test/index, try setting the query parameter name using URL <URL>/test/index?name=foobar, and try the URL without query paramater. Note that sometimes the foobar shows up in the browser, but sometimes it does not.

Sometimes it shows
Another times it does not

DynamoDb Session Class

Fortunately for us, AWS SDK for PHP already provides an interface to use DynamoDB as session handler for PHP with class Aws\DynamoDb\SessionHandler. This class implements the core PHP interface SessionHandlerInterface. Yii2 also provides a nice binding with the SessionHandlerInterface with its class yii\web\Session which is the default class for session. We just need to pass the instance of DynamoDb SessionHandler to handler attribute of Yii2 Session class.

To install AWS SDK PHP composer dependencies, execute the following command.

composer require aws/aws-sdk-php

We then need to create a Session class to wrap the session access. The class will extends yii\web\Session, instantiate the SessionHandler from DynamoDB client, and pass it to handler.

https://gist.github.com/petrabarus/22ad66f6a0768ace91335bc58eb439f3#file-session-php

Setting Up Local Environment

Now we are going to test this in the local environment. We are going to use Localstack docker image to mock DynamoDb. Localstack provides various mocks for AWS services, but here we are going to use only DynamoDb. This way, we don’t need to test in the AWS environment that can take sometime to wait.

The DynamoDb will be accessible through url http://localstack:4569. We will set this in the environment variables alongside the Table name. We also need to provide fake AWS credentials and region in the environment.

environment:
  AWS_REGION: 'ap-southeast-1'
  AWS_ACCESS_KEY_ID: 'fake-access-key'
  AWS_SECRET_ACCESS_KEY: 'fake-secret-key'
  DYNAMODB_SESSION_TABLE_NAME: 'Sessions'
  DYNAMODB_ENDPOINT_URL: 'http://localstack:4569'

To initialize the DynamoDb table for storing sessions we will use the script below. The script will create a new DynamoDb table in the localstack. The table will be called Sessions and will have one hash key named id. It should be executed after the docker compose run.

https://gist.github.com/petrabarus/22ad66f6a0768ace91335bc58eb439f3#file-localstack-setup-sh

Configuring Local Access

The following code will override the default session component to use our Session class. It will obtain the environment variables AWS_REGION, DYNAMODB_ENDPOINT_URL and DYNAMODB_SESSION_TABLE_NAME to the class so the class can pass it to the DynamoDB client and session handler.

//file: /config/web.php
//put this in the `components` list of values.
//    'components' => [
//      .....
        'session' => [
            'class' => app\components\Session::class,
            'clientConfigs' => [
                'version' => '2012-08-10',
                'region' => $_ENV['AWS_REGION'],
                'endpoint' => $_ENV['DYNAMODB_ENDPOINT_URL'] ?? null
            ],
            'sessionConfigs' => [
                'table_name' => $_ENV['DYNAMODB_SESSION_TABLE_NAME']
            ]
        ]

The final configuration will look like below.

https://gist.github.com/petrabarus/22ad66f6a0768ace91335bc58eb439f3#file-web-php

Running in Local

We now have all the things we need to run the local environment. Execute this command to run the application.

docker-compose up -d

You may want to wait until all the containers complete the start up. Then execute this command to initialize the session table.

docker-compose exec localstack sh ./localstack-setup.sh

The site can be accessed in the http://localhost:8080. You can try the test controller by http://localhost:8080/test/index and http://localhost:8008/test/index?name=petra. Of course the session will be persistent since we only have 1 server to access.

Writing CDK Code

We are going to write the CDK code to provision DynamoDB table to store the sessions. But first we need to install the DynamoDB CDK module in the Node packages.

npm update
npm install @aws-cdk/aws-dynamodb

Here we only need to update the WebApp construct. The final code will look like this.

https://gist.github.com/petrabarus/22ad66f6a0768ace91335bc58eb439f3#file-webapp-ts

Now the code explanation.

The following code will provision a new Table with a single partition key called id and type String. This id attribute will store the session ID.

    private createSessionTable(): dynamodb.Table {
        return new dynamodb.Table(this, 'Sessions', {
            partitionKey: {
                name: 'id',
                type: dynamodb.AttributeType.STRING,
            }
        });
    }

After that, the following code will grant permission for the Fargate task’s Task Role to update and write session to DynamoDB. We only need five permissions for this GetItem, UpdateItem, DeleteItem, Scan, and BatchWriteItem.

const actions = [
    "dynamodb:GetItem",
    "dynamodb:UpdateItem",
    "dynamodb:DeleteItem",
    "dynamodb:Scan",
    "dynamodb:BatchWriteItem"
]
this.sessionTable.grant(taskDefinition.taskRole, ...actions);

Finally, in the following code, we will pass through the environment variable AWS_REGION the region for the DynamoDB client to resolve and DYNAMO_SESSION_TABLE_NAME for the created DynamoDB table name.

taskImageOptions: {
    image: ecs.ContainerImage.fromAsset('.'),
    environment: {
        AWS_REGION: region,
        DYNAMODB_SESSION_TABLE_NAME: this.sessionTable.tableName,
    }
},

To apply this new resources and configurations, execute the following command.

cdk deploy MyWebStack

If you open the DynamoDB console, you can see the created new Table.

New Created Table

You can see the sessions are already filling up.

Sessions

You can check the assigned value from the application by scanning the record ID of the row with the session ID. The image below shows that we have set name in the data attribute.

Our Session

Wrapping Up

That concludes the tutorial on how to use DynamoDB for sessions storage. NOTE If you are still experimenting with this, please don’t forget to destroy the resources by executing cdk destroy to avoid incurring cost.

I hope the explanation helps your development effort. I will remind you that this post is part of series Building Modern PHP/Yii2 Application using AWS. If there is any use cases that you would like to see me cover, please don’t hesitate put it in the comments. Looking forward for the next post!

Join the Conversation

1 Comment

  1. Lagi nyari bughost ruang guru dpt subdomain eh di arahin ke sini gak ngerti juga wkwkwk

Leave a comment

Leave a Reply

%d bloggers like this: