Initial commit
This commit is contained in:
227
references/DOCKER_SESSION_FIX.md
Normal file
227
references/DOCKER_SESSION_FIX.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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:
|
||||
|
||||
1. `SessionGuard::updateSession()` - Called by `AttemptToAuthenticate` action
|
||||
2. `PrepareAuthenticatedSession` - 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
|
||||
<?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:
|
||||
|
||||
```php
|
||||
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 when `IS_DOCKER=true` (line 16)
|
||||
- Skip `PrepareAuthenticatedSession` in login pipeline when `IS_DOCKER=true` (line 35)
|
||||
|
||||
```php
|
||||
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:
|
||||
|
||||
```php
|
||||
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 login
|
||||
- `PrepareAuthenticatedSession` - Fortify's action that also calls `session->migrate()`
|
||||
|
||||
### What We Keep
|
||||
- `session->regenerateToken()` - CSRF token is still regenerated for security
|
||||
- `ConditionalAuthenticateSession` middleware - 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:
|
||||
1. Set `IS_DOCKER=false` or remove the environment variable
|
||||
2. Use standard Laravel `SessionGuard` with full session migration
|
||||
3. Ensure proper session persistence with your production session driver
|
||||
|
||||
## Testing
|
||||
After applying these changes:
|
||||
1. Restart the app container: `docker-compose restart app`
|
||||
2. Navigate to login page
|
||||
3. Enter valid credentials
|
||||
4. User should be logged in and redirected to dashboard without redirect loop
|
||||
5. 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
|
||||
1. Verify `IS_DOCKER=true` is set in `.env`
|
||||
2. Check that autoloader was regenerated: `docker-compose exec app composer dump-autoload`
|
||||
3. Clear application cache: `docker-compose exec app php artisan optimize:clear`
|
||||
4. Restart container: `docker-compose restart app`
|
||||
|
||||
## Alternative Solutions Attempted
|
||||
|
||||
### Failed Approaches
|
||||
1. **Disabling session encryption** - Made no difference
|
||||
2. **Excluding session cookie from encryption** - Session data was the issue, not cookies
|
||||
3. **Switching session drivers** (file → Redis → database) - Issue persisted across all drivers
|
||||
4. **Modifying session configuration** - `same_site`, `secure` flags had no effect
|
||||
5. **Custom `DockerSessionGuard` without 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` - Requires `IS_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.
|
||||
Reference in New Issue
Block a user