Skip to content

Sudo Mode

Sudo Mode provides an additional security layer for sensitive operations by requiring users to re-authenticate before performing critical actions, even when already logged in.

Sudo Mode is a temporary elevated privilege state that:

  • Requires password re-confirmation before sensitive operations
  • Lasts for a configurable duration (default: 2 hours)
  • Expires automatically after inactivity
  • Provides GitHub-like security for critical actions

Configure sudo mode in config/sentinel.php:

'sudo_mode' => [
'enabled' => true,
'duration' => 7200, // Seconds (2 hours)
'session_key' => 'sentinel.sudo_mode_expires_at',
'challenge_route' => 'sudo.challenge',
],
  • duration: How long sudo mode stays active (in seconds)
  • session_key: Session key to store expiration timestamp
  • challenge_route: Route to redirect to when sudo mode is required

Use the sudo middleware to require sudo mode:

use Illuminate\Support\Facades\Route;
// Protect individual routes
Route::delete('/account', [AccountController::class, 'destroy'])
->middleware(['auth', 'sudo']);
// Protect route groups
Route::middleware(['auth', 'sudo'])->group(function () {
Route::delete('/account', ...);
Route::post('/settings/api-keys', ...);
Route::put('/settings/password', ...);
Route::post('/billing/payment-method', ...);
});

Verify user’s password and enable sudo mode:

use Cline\Sentinel\Facades\Sentinel;
use Illuminate\Support\Facades\Hash;
public function confirmPassword(Request $request)
{
$request->validate([
'password' => ['required', 'string'],
]);
if (!Hash::check($request->input('password'), $request->user()->password)) {
return back()->withErrors([
'password' => 'The provided password is incorrect.',
]);
}
// Enable sudo mode
Sentinel::enableSudoMode($request);
// Redirect to intended action
return redirect()->intended('/settings/security');
}

Check if user is currently in sudo mode:

use Cline\Sentinel\Facades\Sentinel;
if (Sentinel::inSudoMode($request)) {
// User is in sudo mode - allow sensitive action
$this->deleteAccount($user);
} else {
// Redirect to sudo challenge
return redirect()->route('sudo.challenge');
}

Get expiration time:

$expiresAt = Sentinel::sudoModeExpiresAt($request);
if ($expiresAt && $expiresAt->isFuture()) {
$minutes = now()->diffInMinutes($expiresAt);
echo "Sudo mode active for {$minutes} more minutes";
}

When user tries to access protected route:

// User visits /settings/api-keys
// Sudo middleware checks if sudo mode is active
// If not, redirects to sudo.challenge route with intended URL

Create a sudo challenge view:

<div class="sudo-challenge">
<h2>Confirm Your Password</h2>
<p>This action requires additional verification. Please enter your password to continue.</p>
<form method="POST" action="{{ route('sudo.confirm') }}">
@csrf
<label>
Password:
<input type="password" name="password" required autofocus>
</label>
@error('password')
<span class="error">{{ $message }}</span>
@enderror
<button type="submit">Confirm</button>
<a href="{{ url()->previous() }}">Cancel</a>
</form>
</div>

Handle the confirmation:

use Cline\Sentinel\Facades\Sentinel;
use Illuminate\Support\Facades\Hash;
public function confirmSudoMode(Request $request)
{
$request->validate([
'password' => ['required', 'string'],
]);
if (!Hash::check($request->input('password'), $request->user()->password)) {
return back()->withErrors([
'password' => 'Incorrect password.',
]);
}
// Enable sudo mode
Sentinel::enableSudoMode($request);
// Redirect to originally intended URL
return redirect()->intended('/dashboard');
}

Complete route setup for sudo mode:

// Show sudo challenge form
Route::get('/sudo/challenge', function () {
return view('auth.sudo-challenge');
})->name('sudo.challenge')->middleware('auth');
// Handle sudo confirmation
Route::post('/sudo/confirm', [SudoController::class, 'confirm'])
->name('sudo.confirm')
->middleware('auth');
// Protected routes requiring sudo
Route::middleware(['auth', 'sudo'])->group(function () {
Route::delete('/account', [AccountController::class, 'destroy'])
->name('account.destroy');
Route::post('/settings/api-keys', [ApiKeyController::class, 'store'])
->name('api-keys.store');
Route::put('/settings/password', [PasswordController::class, 'update'])
->name('password.update');
});
Route::delete('/account', function (Request $request) {
// Sudo middleware already verified password
$request->user()->delete();
Auth::logout();
return redirect('/')->with('status', 'Account deleted successfully.');
})->middleware(['auth', 'sudo']);
public function createApiKey(Request $request)
{
// Sudo mode already active from middleware
$token = $request->user()->createToken('API Token');
return view('api-keys.created', [
'token' => $token->plainTextToken,
]);
}
public function updatePaymentMethod(Request $request)
{
// Sudo middleware ensures user recently confirmed password
$request->user()->updateDefaultPaymentMethod($request->input('payment_method'));
return back()->with('status', 'Payment method updated.');
}
Route::middleware(['auth', 'sudo'])->group(function () {
Route::post('/security/totp/disable', ...);
Route::post('/security/sessions/revoke-all', ...);
Route::post('/security/download-data', ...);
});

Require sudo only for specific conditions:

public function updateSettings(Request $request)
{
$user = $request->user();
// Check if critical settings are being changed
$criticalChange = $request->filled('email') || $request->filled('password');
if ($criticalChange && !Sentinel::inSudoMode($request)) {
return redirect()->route('sudo.challenge')
->with('intended', route('settings.update'));
}
// Proceed with update
$user->update($request->validated());
return back()->with('status', 'Settings updated.');
}

Sudo mode dispatches events for monitoring:

use Cline\Sentinel\Events\SudoModeEnabled;
use Cline\Sentinel\Events\SudoModeChallenged;
// Log when sudo mode is activated
Event::listen(SudoModeEnabled::class, function ($event) {
Log::info('Sudo mode enabled', [
'user_id' => $event->user->id,
'ip' => request()->ip(),
]);
});
// Track sudo challenges (potential security interest)
Event::listen(SudoModeChallenged::class, function ($event) {
Log::info('Sudo mode challenged', [
'user_id' => $event->user->id,
'route' => $event->intendedUrl,
]);
});

Protect sudo confirmation from brute force:

use Illuminate\Support\Facades\RateLimiter;
public function confirmSudoMode(Request $request)
{
$key = 'sudo-confirm:'.$request->user()->id;
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
return back()->withErrors([
'password' => "Too many attempts. Try again in {$seconds} seconds.",
]);
}
$request->validate(['password' => ['required', 'string']]);
if (!Hash::check($request->input('password'), $request->user()->password)) {
RateLimiter::hit($key, 300); // 5 minute lockout
return back()->withErrors(['password' => 'Incorrect password.']);
}
RateLimiter::clear($key);
Sentinel::enableSudoMode($request);
return redirect()->intended('/dashboard');
}

Show sudo mode status in UI:

@if(Sentinel::inSudoMode(request()))
<div class="alert alert-info">
You're in sudo mode. Sensitive actions are unlocked for
{{ now()->diffInMinutes(Sentinel::sudoModeExpiresAt(request())) }} more minutes.
</div>
@endif

In settings page:

<div class="security-indicator">
@if(Sentinel::inSudoMode(request()))
<span class="badge badge-success">Sudo Mode Active</span>
<small>
Expires {{ Sentinel::sudoModeExpiresAt(request())->diffForHumans() }}
</small>
@else
<span class="badge badge-secondary">Standard Access</span>
@endif
</div>
  1. Use for sensitive actions only - Don’t overuse or users get frustrated
  2. Configure appropriate duration - 2 hours is good default
  3. Show clear messaging - Explain why password is needed again
  4. Rate limit confirmations - Prevent brute force attacks
  5. Log sudo activations - Monitor for suspicious patterns
  6. Send notifications - Alert on sudo mode usage (optional)
  7. Combine with multi-factor - Layer security for admin operations

Account Management:

  • Delete account
  • Change email address
  • Change password
  • Disable multi-factor

Financial:

  • Add/remove payment methods
  • Cancel subscription
  • Download invoices

Security:

  • Generate API keys
  • Revoke all sessions
  • Download personal data
  • Change OAuth credentials

Administrative:

  • Transfer ownership
  • Delete resources
  • Bulk operations

Full sudo-protected API key management:

routes/web.php
Route::middleware(['auth'])->group(function () {
Route::get('/api-keys', [ApiKeyController::class, 'index'])
->name('api-keys.index');
Route::middleware(['sudo'])->group(function () {
Route::post('/api-keys', [ApiKeyController::class, 'store'])
->name('api-keys.store');
Route::delete('/api-keys/{key}', [ApiKeyController::class, 'destroy'])
->name('api-keys.destroy');
});
});
Route::get('/sudo/challenge', [SudoController::class, 'show'])
->name('sudo.challenge')
->middleware('auth');
Route::post('/sudo/confirm', [SudoController::class, 'confirm'])
->name('sudo.confirm')
->middleware('auth');
// app/Http/Controllers/ApiKeyController.php
class ApiKeyController extends Controller
{
public function index(Request $request)
{
$keys = $request->user()->tokens;
return view('api-keys.index', [
'keys' => $keys,
'inSudoMode' => Sentinel::inSudoMode($request),
]);
}
public function store(Request $request)
{
// Sudo middleware already confirmed password
$request->validate([
'name' => ['required', 'string', 'max:255'],
]);
$token = $request->user()->createToken($request->input('name'));
return back()->with([
'status' => 'API key created successfully!',
'token' => $token->plainTextToken,
]);
}
public function destroy(Request $request, PersonalAccessToken $key)
{
// Sudo middleware already confirmed password
$key->delete();
return back()->with('status', 'API key revoked.');
}
}
// app/Http/Controllers/SudoController.php
class SudoController extends Controller
{
public function show()
{
return view('auth.sudo-challenge');
}
public function confirm(Request $request)
{
$key = 'sudo-confirm:'.$request->user()->id;
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
return back()->withErrors([
'password' => "Too many attempts. Try again in {$seconds} seconds.",
]);
}
$request->validate(['password' => ['required', 'string']]);
if (!Hash::check($request->input('password'), $request->user()->password)) {
RateLimiter::hit($key, 300);
return back()->withErrors(['password' => 'Incorrect password.']);
}
RateLimiter::clear($key);
Sentinel::enableSudoMode($request);
return redirect()->intended('/api-keys');
}
}