Session scoping
When using a multi-database setup, it’s critical to properly separate user sessions.
Session forgery
If your sessions are stored centrally, a user may be able to access another user’s data in another tenant:
- User with ID 1 in
tenant1.yourapp.com
changes the session cookie domain totenant2.yourapp.com
- The user then visits
tenant2.yourapp.com
- The user will be logged in as user with ID 1
The reason why this happens is that sessions store user IDs, and you may have different users in different tenants with the same IDs.
This is only an issue if both user IDs and tenant domains/IDs are enumerable or otherwise non-secret, but it’s still worth taking precautions against regardless of your setup.
Using a tenant-scoped session driver
Database session driver
If you use the DatabaseTenancyBootstrapper
, using the database session driver will lead to sessions being automatically scoped.
Only thing to note here is that due to some details of how the database session driver works, you may need to also enable the DatabaseSessionBootstrapper
:
Otherwise, you may get confusing exceptions when switching between tenants (specifically: Call to a member function prepare() on null
). That said, the scoping is handled entirely by the DatabaseTenancyBootstrapper
.
There is one exception to the above, and that’s when you set a SESSION_CONNECTION
. In that case, the session bootstrapper is necessary to properly separate tenant sessions.
Redis session driver
Similar to above, if you store your sessions in Redis, simply enabling the RedisTenancyBootstrapper
and configuring its prefixed connections will lead to sessions being properly scoped:
In this case, default
is included in the prefixed_connections
since it’s the Redis connection used by the session driver (if your SESSION_CONNECTION
/session.connection
is different, make sure to include it in the prefixed connections).
Cache-based session drivers
Many of Laravel’s session drivers leverage Laravel’s cache logic for storing sessions. Namely:
- apc
- dynamodb
- memcached
- redis
As such, these sessions can be scoped using the CacheTenancyBootstrapper
. To enable this behavior, set tenancy.cache.scope_sessions
to true:
File session driver
The FilesystemTenancyBootstrapper
takes care of scoping sessions out of the box. Simply make sure tenancy.filesystem.scope_sessions
is set to true
:
Using a middleware to prevent session forgery
Alternatively (or additionally), you may use the Stancl\Tenancy\Middleware\ScopeSessions
middleware on your tenant routes to make sure that any attempts to manipulate the session will result in a 403 unauthorized response.
This will work with all storage drivers, but only assuming you use a domain per tenant. If you use path identification, you need to store sessions in the database (if using multi-DB tenancy), or you need to use single-DB tenancy.
Using globally unique user IDs
Since sessions use user IDs, you can also solve this by using non-enumerable, globally unique IDs like UUIDs.
Alternatively, you can use a custom user provider to use a different column than the primary key for sessions. That column would still need to be globally unique.