When a Joomla login menu item is configured with a Menu Item login redirect (the default redirect type), logging in via the System - Passkey (Passwordless) Login plugin (plg_system_webauthn) redirects the user to the site home page instead of the configured target page.
Username/password login on the same form works correctly and redirects to the configured page.
There is also a secondary issue: even when the redirect target is reached, the Webauthn plugin redirects to the raw non-SEF URL (e.g. index.php?Itemid=333) instead of the SEF equivalent (e.g. /student-home).
User is redirected to the selected menu item's SEF URL (e.g. https://example.com/student-home).
User is redirected to the site home page (Uri::base()).
If the redirect is manually worked around (see below), the user reaches the correct page but via the raw non-SEF URL (https://example.com/index.php?Itemid=333) instead of the SEF URL.
return field)Two bugs in plg_system_webauthn, both in the PluginTraits files:
AjaxHandlerChallenge does not handle numeric menu item IDsWhen the login redirect is configured as a "Menu Item", Joomla stores the target menu item's ID as a bare integer in the login form's hidden return field (base64-encoded). For example, if the target menu item is ID 333, the return field contains base64_encode("333").
The core com_users UserController::login() explicitly handles this case (components/com_users/src/Controller/UserController.php, lines 54-64):
if (is_numeric($data['return'])) {
$itemId = (int) $data['return'];
$data['return'] = 'index.php?Itemid=' . $itemId;
if (Multilanguage::isEnabled()) {
$language = $this->getModel('Login', 'Site')->getMenuLanguage($itemId);
if ($language !== '*') {
$data['return'] .= '&lang=' . $language;
}
}
} elseif (!Uri::isInternal($data['return'])) {
$data['return'] = '';
}The Webauthn challenge handler (plugins/system/webauthn/src/PluginTraits/AjaxHandlerChallenge.php) decodes the returnUrl but skips the is_numeric() check and passes the value directly to Uri::isInternal(). Since Uri::isInternal("333") returns false (a bare integer has no host and doesn't start with index.php), the handler falls back to Uri::base() — the home page.
AjaxHandlerLogin does not route the redirect URL through Joomla's routerEven after fixing Bug 1, the login handler redirects with the raw internal URL:
$this->getApplication()->redirect($returnUrl);The core UserController::login() routes the URL through Route::_() before redirecting (line 108):
$this->app->redirect(Route::_($this->app->getUserState('users.login.form.return'), false));The Webauthn login handler does not, so the user sees index.php?Itemid=333 instead of the SEF URL.
plugins/system/webauthn/src/PluginTraits/AjaxHandlerChallenge.phpAdd the is_numeric() → index.php?Itemid=X conversion (mirroring UserController::login()), including multilanguage support.
Diff:
--- a/plugins/system/webauthn/src/PluginTraits/AjaxHandlerChallenge.php
+++ b/plugins/system/webauthn/src/PluginTraits/AjaxHandlerChallenge.php
@@ -12,10 +12,13 @@
use Joomla\CMS\Event\Plugin\System\Webauthn\AjaxChallenge;
use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\CMS\User\UserHelper;
+use Joomla\Database\DatabaseInterface;
+use Joomla\Database\ParameterType;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
@@ -59,8 +62,36 @@
$returnUrl = $input->getBase64('returnUrl', $returnUrl);
$returnUrl = base64_decode($returnUrl);
- // For security reasons the post-login redirection URL must be internal to the site.
- if (!Uri::isInternal($returnUrl)) {
+ // Handle numeric menu item IDs (same as com_users UserController::login()).
+ // When the login redirect is configured as a "Menu Item" the stored value
+ // is a bare integer (the Itemid) which Uri::isInternal() rejects, causing
+ // the post-login redirect to fall back to the site root.
+ if (is_numeric($returnUrl)) {
+ $itemId = (int) $returnUrl;
+ $returnUrl = 'index.php?Itemid=' . $itemId;
+
+ if (Multilanguage::isEnabled()) {
+ $db = Factory::getContainer()->get(DatabaseInterface::class);
+ $query = $db->createQuery()
+ ->select($db->quoteName('language'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('client_id') . ' = 0')
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $itemId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $language = $db->loadResult();
+ } catch (\Exception) {
+ $language = '';
+ }
+
+ if ($language !== '*' && !empty($language)) {
+ $returnUrl .= '&lang=' . $language;
+ }
+ }
+ } elseif (!Uri::isInternal($returnUrl)) {
+ // For security reasons the post-login redirection URL must be internal to the site.
// If the URL wasn't internal redirect to the site's root.
$returnUrl = Uri::base();
}plugins/system/webauthn/src/PluginTraits/AjaxHandlerLogin.phpWrap the redirect URL with Route::_() to produce SEF URLs, matching the core UserController::login() behavior.
Diff:
--- a/plugins/system/webauthn/src/PluginTraits/AjaxHandlerLogin.php
+++ b/plugins/system/webauthn/src/PluginTraits/AjaxHandlerLogin.php
@@ -17,6 +17,7 @@
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\CMS\User\UserFactoryInterface;
@@ -129,7 +130,7 @@
$session->set('plg_system_webauthn.userId', null);
// Redirect back to the page we were before.
- $this->getApplication()->redirect($returnUrl);
+ $this->getApplication()->redirect(Route::_($returnUrl, false));
}
}Route::_() is safe for all returnUrl values because it returns the URL unchanged if it doesn't start with index.php (see libraries/src/Router/Route.php, Route::link() early return). This covers the Uri::base() fallback and any full-URL Uri::current() value.
plg_system_webauthn (confirmed on 6.1.3; the code pattern has been unchanged since 4.x).com_users UserController::login() — no new patterns introduced.| Labels |
Removed:
?
|
||
| Labels |
Added:
No Code Attached Yet
|
||