How to enforce X-Joomla-Token authentication in custom Joomla 5 Web Services plugins (without using 'public' => true)

I’m developing a custom Joomla 5 Web Services plugin (under plugins/webservices/helloworldapi) using the onBeforeApiRoute event to expose a route like this:

$route = new \Joomla\Router\Route(
  ['GET'],
  'v1/helloworldapi/sayhello',
  'api.onApiHelloworld',
  [],
  [
    'component' => 'com_helloworldapi',
    'format' => ['application/json'],
    // 'public' => true // I want this OFF to require authentication
  ]
);
$router->addRoute($route);

In my event handler, I try to retrieve the authenticated user:

public function onApiHelloworld()
{
  $app = \Joomla\CMS\Factory::getApplication();
  $user = $app->getIdentity();

  if ($user->guest) {
    echo new \JResponseJson(['error' => 'Unauthorized'], 401);
  } else {
    echo new \JResponseJson(['id' => $user->id, 'name' => $user->name]);
  }

  $app->close();
}

Behavior I’m Expecting:

  • If the request includes a valid X-Joomla-Token for an enabled API token, the request should proceed
  • and $user should be populated.
  • If the token is missing or invalid, $user->guest should be true.

Problem:

  • $user is always a guest, even with the correct token in the header.
  • The same token works perfectly for built-in endpoints like /api/index.php/v1/users.
  • If I set ‘public’ => true, the route works (but bypasses authentication — which I don’t want).

Straightforward Answer:

Your custom API route in Joomla 5 bypasses authentication because you’re using onBeforeApiRoute — which registers the route before the core authentication logic kicks in.


The Problem:

  • onBeforeApiRoute runs too early, before Joomla attaches the authenticated user based on X-Joomla-Token or other auth headers.
  • So, $app->getIdentity() always returns a guest, even if the token is valid.

The Fix:

Move your route registration to the onAfterApiAuthenticate event instead of onBeforeApiRoute.

public function onAfterApiAuthenticate($app, $router)
{
    $route = new \Joomla\Router\Route(
        ['GET'],
        'v1/helloworldapi/sayhello',
        'api.onApiHelloworld',
        [],
        [
            'component' => 'com_helloworldapi',
            'format' => ['application/json'],
            // Leave 'public' => false (default), so auth is required
        ]
    );

    $router->addRoute($route);
}

This ensures that Joomla’s API authentication (via token or session) is already complete before your route is added, so $app->getIdentity() will correctly return the authenticated user.


Summary:

What you did Result
Used onBeforeApiRoute Auth not yet processed
Moved to onAfterApiAuthenticate Auth is ready