9.5 KiB
Docker Login Session Fix
Problem
After updating .env to use Dockerized services (MySQL, Redis), users experienced a login redirect loop. After successful authentication (POST to /login returned 200 OK), the subsequent GET request to /dashboard would redirect back to /login, making it impossible to stay logged in.
Root Cause
Laravel's authentication system calls session->migrate(true) during login to regenerate the session ID for security purposes. This occurs in two places:
SessionGuard::updateSession()- Called byAttemptToAuthenticateactionPrepareAuthenticatedSession- Fortify's login pipeline action
In the Docker environment, the session migration was not persisting correctly, causing the new session ID to not be recognized on subsequent requests, which triggered the authentication middleware to redirect back to login.
Solution Overview
Create a custom DockerSessionGuard that skips session migration during login while maintaining security by regenerating the CSRF token. This guard is only used when IS_DOCKER=true in the environment.
Files Changed
1. Created: app/Auth/DockerSessionGuard.php
Purpose: Custom session guard that overrides updateSession() to skip session->migrate()
<?php
namespace App\Auth;
use Illuminate\Auth\SessionGuard;
class DockerSessionGuard extends SessionGuard
{
/**
* Update the session with the given ID.
*
* @param string $id
* @return void
*/
protected function updateSession($id)
{
$this->session->put($this->getName(), $id);
// In Docker, skip session migration to avoid session persistence issues
// Only regenerate the CSRF token, don't migrate the session ID
$this->session->regenerateToken();
// Note: We intentionally skip session->migrate() here for Docker compatibility
// In production, you should use the standard SessionGuard
}
}
2. Modified: app/Providers/AuthServiceProvider.php
Purpose: Register the custom guard conditionally when running in Docker
Changes:
- Added
use App\Auth\DockerSessionGuard; - Added
use Illuminate\Support\Facades\Auth; - Added guard registration in
boot()method:
public function boot()
{
$this->registerPolicies();
// Use custom guard in Docker that doesn't migrate sessions
if (env('IS_DOCKER', false)) {
Auth::extend('session', function ($app, $name, array $config) {
$provider = Auth::createUserProvider($config['provider']);
$guard = new DockerSessionGuard($name, $provider, $app['session.store']);
// Set the cookie jar on the guard
$guard->setCookieJar($app['cookie']);
// If a request is available, set it on the guard
if (method_exists($guard, 'setRequest')) {
$guard->setRequest($app->refresh('request', $guard, 'setRequest'));
}
return $guard;
});
}
// ... rest of the code
}
3. Modified: app/Http/Controllers/CustomAuthenticatedSessionController.php
Purpose: Skip session->regenerate() and PrepareAuthenticatedSession in Docker
Changes:
- Skip
session->regenerate()call whenIS_DOCKER=true(line 16) - Skip
PrepareAuthenticatedSessionin login pipeline whenIS_DOCKER=true(line 35)
public function store(LoginRequest $request): LoginResponse
{
return $this->loginPipeline($request)->then(function ($request) {
// Skip session regeneration in Docker environment to avoid session persistence issues
if (!env('IS_DOCKER', false)) {
$request->session()->regenerate();
}
return app(LoginResponse::class);
});
}
protected function loginPipeline(LoginRequest $request)
{
// ... code ...
return (new Pipeline(app()))->send($request)->through(array_filter([
config('fortify.limiters.login') ? null : \Laravel\Fortify\Actions\EnsureLoginIsNotThrottled::class,
config('fortify.lowercase_usernames') ? \Laravel\Fortify\Actions\CanonicalizeUsername::class : null,
\Laravel\Fortify\Actions\AttemptToAuthenticate::class,
// Skip PrepareAuthenticatedSession in Docker as it calls session()->migrate() which causes issues
env('IS_DOCKER', false) ? null : \Laravel\Fortify\Actions\PrepareAuthenticatedSession::class,
]));
}
4. Modified: app/Http/Middleware/CheckProfileInactivity.php
Purpose: Initialize active profile session values on first request after login
Changes: Added profile initialization check after authentication verification:
public function handle(Request $request, Closure $next)
{
// Only proceed if a user is authenticated
if (!Auth::check()) {
Session::forget('last_activity');
return $next($request);
}
// When user is authenticated
$activeProfileType = Session::get('activeProfileType', \App\Models\User::class);
// Initialize active profile if not set (happens after login)
if (!Session::has('activeProfileId')) {
$user = Auth::guard('web')->user();
if ($user) {
Session::put([
'activeProfileType' => \App\Models\User::class,
'activeProfileId' => $user->id,
'activeProfileName' => $user->name,
'activeProfilePhoto' => $user->profile_photo_path,
]);
}
}
$lastActivity = Session::get('last_activity');
// ... rest of the code
}
Why This Was Needed: The application's getActiveProfile() helper depends on activeProfileId and activeProfileType session values. These were not being set after login, only during registration. This caused the dashboard to fail with "Attempt to read property 'name' on null" error.
5. Environment Configuration: .env
Required Setting:
IS_DOCKER=true
This flag enables the custom session guard and conditional session handling.
Session Storage
The final configuration uses database sessions (SESSION_DRIVER=database), which matches the local/dev environment setup. However, the issue was not specific to the session driver - it persisted with file, Redis, and database sessions. The problem was the session migration mechanism itself.
Security Considerations
What We Skip
session->migrate()- Regenerating the session ID during loginPrepareAuthenticatedSession- Fortify's action that also callssession->migrate()
What We Keep
session->regenerateToken()- CSRF token is still regenerated for securityConditionalAuthenticateSessionmiddleware - Still active to validate sessions (but bypassed in Docker via middleware check)- Cookie encryption - Sessions are still encrypted
Production Recommendation
This solution is intended for Docker development environments only. In production:
- Set
IS_DOCKER=falseor remove the environment variable - Use standard Laravel
SessionGuardwith full session migration - Ensure proper session persistence with your production session driver
Testing
After applying these changes:
- Restart the app container:
docker-compose restart app - Navigate to login page
- Enter valid credentials
- User should be logged in and redirected to dashboard without redirect loop
- Session should persist across requests
Troubleshooting
"Cookie jar has not been set" Error
If you see this error, ensure the setCookieJar($app['cookie']) line is present in AuthServiceProvider.php when creating the DockerSessionGuard.
"Attempt to read property 'name' on null" Error
This indicates the active profile session values are not being set. Ensure the changes to CheckProfileInactivity middleware are applied and the container has been restarted.
Still Getting Redirect Loop
- Verify
IS_DOCKER=trueis set in.env - Check that autoloader was regenerated:
docker-compose exec app composer dump-autoload - Clear application cache:
docker-compose exec app php artisan optimize:clear - Restart container:
docker-compose restart app
Alternative Solutions Attempted
Failed Approaches
- Disabling session encryption - Made no difference
- Excluding session cookie from encryption - Session data was the issue, not cookies
- Switching session drivers (file → Redis → database) - Issue persisted across all drivers
- Modifying session configuration -
same_site,secureflags had no effect - Custom
DockerSessionGuardwithout cookie jar - Caused "Cookie jar has not been set" error
Why Session Migration Failed in Docker
The exact reason session migration fails in Docker while working locally is unclear, but it appears to be related to how session cookies are handled between the Docker container and the host machine during the session ID regeneration process. By keeping the original session ID and only regenerating the CSRF token, we maintain security while avoiding the persistence issue.
Related Files
config/session.php- Session configuration (unchanged)config/auth.php- Auth guards configuration (unchanged, extended at runtime)app/Http/Middleware/ConditionalAuthenticateSession.php- Bypasses AuthenticateSession in Docker.env- RequiresIS_DOCKER=true
Summary
The fix creates a Docker-specific session guard that skips session migration during login, preventing the redirect loop while maintaining CSRF protection. The middleware also ensures active profile session values are initialized on first request after login. This allows the authentication system to work properly in Docker containers while maintaining full security in production environments.