How to Enforce X-Joomla-Token Authentication in Custom Joomla 5 Web Services Plugins (Without Using ‘public’ => true)

When building custom Joomla 5 Web Services plugins, developers often want to protect API endpoints using Joomla’s built-in authentication, especially the X-Joomla-Token header. Unfortunately, it’s common to run into issues where the custom route doesn’t recognize the authenticated user, even when a valid token is sent.

In this guide, we’ll show you exactly how to secure your custom Joomla API endpoints properly—without using 'public' => true, which would bypass authentication.

We’ll walk through:

  • Creating a secure custom API plugin in Joomla 5
  • Enforcing token-based authentication
  • Ensuring $app->getIdentity() returns the authenticated user
  • Avoiding common pitfalls

Why Not Use 'public' => true?

Setting 'public' => true in your custom API route makes it publicly accessible — which means no authentication is required. This defeats the purpose of API token validation, especially for endpoints that deal with user data, internal logic, or write operations.

Instead, we want to enforce Joomla’s built-in token auth system using X-Joomla-Token (or session/cookie-based auth if needed).


Step-by-Step Guide to Secure Custom API Endpoints

Plugin Structure Example

Your custom plugin should be placed in:

/plugins/webservices/helloworldapi/

Structure:

/helloworldapi
├── helloworldapi.php
├── helloworldapi.xml
└── services
    └── provider.php

1. Register Routes in the Right Event

Instead of onBeforeApiRoute, use the onAfterApiAuthenticate event to register your custom routes.

Why? Because Joomla processes authentication before this event — which means the user will be available via getIdentity().

helloworldapi.php

defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;

class PlgWebservicesHelloworldapi extends CMSPlugin
{
    protected $autoloadLanguage = true;

    public function onAfterApiAuthenticate($app, $router)
    {
        $route = new \Joomla\Router\Route(
            ['GET'],
            'v1/helloworldapi/sayhello',
            'api.onApiHelloworld',
            [],
            [
                'component' => 'com_helloworldapi',
                'format' => ['application/json'],
                //  DO NOT SET 'public' => true
            ]
        );

        $router->addRoute($route);
    }

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

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

        $app->close();
    }
}

2. Enable the Plugin

  • Go to System → Plugins
  • Enable your Webservices - Helloworldapi plugin

3. Test the Endpoint

Use a REST client like Postman or cURL:

Test with Token

GET /api/index.php/v1/helloworldapi/sayhello
X-Joomla-Token: your_valid_api_token
Accept: application/json

Expected response:

{
  "id": 123,
  "name": "John Doe"
}

Test Without Token

Returns:

{
  "error": "Unauthorized"
}

Common Pitfall: Registering the Route Too Early

Don’t do this:

public function onBeforeApiRoute($app, $router)

At this point, authentication hasn’t been processed, so:

  • $app->getIdentity() will return a guest
  • Even valid tokens will be ignored

Use Cases Where This Matters

This approach is useful for:

  • Admin-only API access
  • Restricted mobile app endpoints
  • Authenticated user data access
  • Custom business logic inside your own extensions

More Resources

Looking to integrate Joomla APIs into full-stack applications? Check out:


Summary

Step What to Do
1. Use onAfterApiAuthenticate to add your routes
2. Never set 'public' => true unless you want no auth
3. Use $app->getIdentity() to get the user
4. Return 401 Unauthorized if guest

With this setup, your custom Joomla 5 API plugins will be secure, token-authenticated, and ready for production use.