Did you every manage to fix this? I am wondering as i am facing the exact same problem at the moment
Cisco Duo Security MFA
I am working to implement the Cisco Duo Security MFA option using their Web SDK PHP wrapper found here: https://github.com/duosecurity/duo_universal_php
I am comfortable with their auth workflow so the issue I am having is how to leverage it in Laravel 8.x with the basic authentication package.
Currently I am performing the MFA call in the LoginController in the authenticated method as follows:
use Duo\DuoUniversal\Client;
use Duo\DuoUniversal\DuoException;
public function authenticated(Request $request, $user)
{
try {
$duo_client = new Client(
config('duo.client_id'),
config('duo.client_secret'),
config('duo.api_hostname'),
config('duo.redirect_uri')
);
$health_check = $duo_client->healthCheck();
if ($health_check['stat'] == 'OK') {
$state = $duo_client->generateState();
request()->session()->put('duo_state', $state);
request()->session()->put('duo_user', $user->email);
# Redirect to prompt URI which will redirect to the client's redirect URI after 2FA
$prompt_uri = $duo_client->createAuthUrl($user->email, $state);
return redirect()->away($prompt_uri, 302);
}
} catch (DuoException $e) {
dd($e);
}
}
This works fine except the user is already authenticated in Laravel which means they could use the history navigation to return the application without ever completing the MFA verification steps.
Currently I am using a session variable to store the code returned from Duo MFA and a separate middleware to check if the MFA verification steps are completed before allowing them to navigate the site. If the session variable is not found they are logged out and redirected to the login page with a flash message.
Here is the callback method
public function callback()
{
$duo_client = new Client(
config('duo.client_id'),
config('duo.client_secret'),
config('duo.api_hostname'),
config('duo.redirect_uri')
);
$code = request()->query('duo_code');
$state = request()->query('state');
$saved_state = request()->session()->get('duo_state');
$username = request()->session()->get('duo_user');
if (empty($saved_state) || empty($username)) {
Auth::logout();
return redirect()->route('login')->with('status', 'No saved state please login again');
}
if ($state != $saved_state) {
Auth::logout();
return redirect()->route('login')->with('status', 'Duo state does not match saved state');
}
try {
$decoded_token = $duo_client->exchangeAuthorizationCodeFor2FAResult($code, $username);
// Ensure that the user went through MFA and was not able to go back and use Laravel Session
request()->session()->put('duo_mfa', $code);
return redirect()->route('home')->with('status', 'Login with Duo Security was successful');
} catch (DuoException $e) {
dd('Error decoding Duo result. Confirm device clock is correct.');
}
}
Here is the middleware
// Ensure that the user went through MFA and was not able to go back and use Laravel Session
public function handle(Request $request, Closure $next)
{
// Ensure that the user went through MFA and was not able to go back and use Laravel Session
$authorized = request()->session()->get('duo_mfa');
if ($authorized) {
return $next($request);
} else {
Auth::logout();
return redirect()->route('login')->with('status', 'User is unauthorized');
}
}
Is this how you would go about this? If not, how should I be thinking about the process?
Please or to participate in this conversation.