Skip to main content

Authentication in Joomla

14 June 2026

When you type your username and password into Joomla, something has to decide whether they are correct. That something is not the login form, and it is not the core component behind it. It is a small chain of authentication plugins, and they are the real gatekeepers of every Joomla site.

This article explains how Joomla's authentication plugins really work. It covers the basics for owners and editors, the daily setup for administrators, and the technical details for developers. You will learn what the two plugin groups do, how the password check actually happens, how "Remember Me" and LDAP fit in, and how the Web Services API authenticates a request with a token instead of a form.

Joomla never hard-codes how a password is checked. It asks plugins, and the plugins answer.

The goal is simple: help you understand authentication plugins well enough to configure them, secure them, and debug them with confidence.

1. The Basics

1.1 What is an Authentication Plugin?

An authentication plugin answers one question: "are these credentials valid?" When a visitor submits the login form, Joomla does not check the password itself. It fires an event and lets every enabled authentication plugin look at the credentials. Each plugin replies with success, failure, or "this is not my job".

This design is the reason you can add Google login, LDAP, single sign-on, or your own custom method to Joomla without ever editing the core. You write a plugin, it joins the chain, and Joomla starts asking it too.

1.2 Two Separate Plugin Groups

Joomla ships authentication plugins in two different groups, and beginners often confuse them:

Plugin groupFolderUsed by
authentication plugins/authentication/ The website and backend login form (HTML, with a session).
api-authentication plugins/api-authentication/ The Web Services API (REST, no form, no session).

Both groups answer the same event name, but they run in different applications. The authentication group guards the human login form. The api-authentication group guards machine-to-machine API calls. Enabling one has no effect on the other.

1.3 The One Event That Matters

Every authentication plugin in both groups subscribes to the same event: onUserAuthenticate. Joomla fires it with the submitted credentials, and each plugin fills in a shared response object that says yes or no. That single event is the heart of this whole article.

Back to top

2. The Authentication Response

2.1 How Joomla Collects the Answers

When login starts, Joomla walks through the enabled authentication plugins one by one. It hands each plugin an AuthenticationResponse object and asks it to set a status. The moment one plugin reports success, the user is authenticated. If none of them say yes, the login fails.

The response object carries more than a yes or no. A successful plugin also fills in the user's details so Joomla can finish the login:

PropertyMeaning
status The verdict: success, failure, denied, and so on.
username The login name being checked.
email The user's email address.
fullname The user's display name.
language The user's preferred language.
error_message A reason, shown when the status is a failure.

A success here means only that the credentials are valid. It is the first factor. If multi-factor authentication is set up, Joomla still challenges the user for a second factor afterwards. That step is handled by a separate plugin group (multifactorauth), not by the authentication plugins in this article.

2.2 The Status Constants

A plugin does not return true or false. It sets one of the status constants on the Joomla\CMS\Authentication\Authentication class. Knowing them makes plugin logs and custom plugins much easier to read:

ConstantMeaning
STATUS_SUCCESS Credentials are valid. The user may continue.
STATUS_FAILURE Credentials are wrong. Login is blocked.
STATUS_DENIED The account exists but is not allowed in (blocked, not activated, reset required).
STATUS_EXPIRED The account has expired.
STATUS_UNKNOWN The plugin has no opinion. This is "not my job", not an error.
STATUS_CANCEL Authentication was cancelled. Rarely used.

The STATUS_UNKNOWN value is the polite default. A plugin that does not recognise the credentials leaves the status unknown and lets the next plugin try. This is how several plugins coexist without fighting each other.

Back to top

3. Authentication - Joomla (the Default)

3.1 What It Does

The Authentication - Joomla plugin (plg_authentication_joomla) is the one that checks the normal username and password against the database. It is enabled on every site, and you should keep it that way. Disable it and nobody can log in with a password again.

Its logic is short and careful. In plain terms, it does this:

1. If the password is empty, fail immediately.
2. Look up the username in #__users and read the stored hash.
3. Verify the typed password against that hash.
4. Match    → STATUS_SUCCESS, fill in email/name/language.
   No match → STATUS_FAILURE.

3.2 How Passwords Are Stored

Joomla never stores a plain-text password. It saves a salted bcrypt hash in the password column of #__users, created with PHP's native password hashing functions. A stored value looks like this:

$2y$10$Ux3...   (algorithm, cost, salt, and hash in one string)

The plugin verifies with UserHelper::verifyPassword(), which also re-hashes on login. If the stored hash uses an older algorithm or a lower cost than the current setting, Joomla transparently rewrites it with the stronger version while you log in. Your credentials get safer over time without anyone doing a thing.

3.3 A Small but Clever Security Detail

When the username does not exist, the plugin still runs a password hash before returning failure. This looks wasteful, but it is deliberate. It makes a login for a real-but-wrong password take the same time as a login for a username that does not exist. An attacker cannot measure the response time to learn which usernames are valid. This is called timing-attack mitigation.

3.4 It Stops the Chain on Success

When this plugin succeeds, it calls stopPropagation() on the event. That tells Joomla not to bother the remaining authentication plugins, because the answer is already a clear yes. A failure does not stop the chain, so a later plugin (for example LDAP) still gets its turn.

Back to top

4.1 What It Powers

The Authentication - Cookie plugin (plg_authentication_cookie) is what makes the Remember Me checkbox work on the frontend login. With it enabled, a visitor who ticks the box stays logged in across browser sessions, with no need to type the password again. Without it, the checkbox is there but does nothing.

It only works on the frontend. The plugin skips the administrator login entirely, so Remember Me never keeps anyone signed in to the backend. That is a sensible security choice.

4.2 The Token-and-Series Trick

A Remember Me cookie is more than a stored password. It uses a token and a series, both kept in the #__user_keys table. The cookie value holds two parts joined by a dot:

{token}.{series}

The series stays the same for the life of the cookie and identifies the device. The token is a one-time secret that Joomla rotates on each visit. When you return:

  • Joomla finds the row in #__user_keys by series and by a hash of your browser's user agent.
  • It verifies the token against the stored hash. If it matches, you are logged in and a fresh token is issued.
  • If the series is known but the token is wrong, Joomla treats it as a possible stolen cookie. It deletes every "Remember Me" token for that user and logs a security alert, so a thief and the real user cannot both stay in.

4.3 The Settings You Can Tune

ParameterDefaultMeaning
cookie_lifetime 60 How many days the Remember Me cookie stays valid.
key_length 16 The length of the generated token. Options are 8, 16, 32, 64.

A shorter lifetime is safer but asks people to log in more often. The default of 60 days is a reasonable balance for most sites.

Back to top

5. Authentication - LDAP (External Directory)

5.1 When You Need It

The Authentication - LDAP plugin (plg_authentication_ldap) checks credentials against an external LDAP or Active Directory server instead of the Joomla database. Organisations use it so staff log in to Joomla with the same account they use for email and the network. It is disabled by default, because most sites do not need it.

5.2 Two Ways to Connect

The plugin offers two strategies through its auth_method parameter:

MethodHow it authenticates
Bind Builds the user's full directory name (DN) from a template and binds straight to LDAP with the typed password. Simple, no service account needed.
Search First binds with a service account, searches for the user, then re-binds as that user with the typed password. Use it when the DN is not predictable.

A successful bind is the proof: if the LDAP server accepts the user's DN and password, the credentials are valid, and the plugin returns STATUS_SUCCESS.

5.3 Key Parameters

ParameterPurpose
host / port The LDAP server address and port.
encryption Connection security: none, tls, or ssl.
base_dn The branch of the directory to search in.
users_dn The DN template for the bind method, with a [username] placeholder.
searchstring The filter for the search method, with a [search] placeholder.
ldap_uid / ldap_email / ldap_fullname Which directory fields map to the username, email, and full name.

Always use tls or ssl in production. A plain LDAP connection sends the password across the network unencrypted.

Back to top

6. Authentication for the Web Services API

6.1 A Different World

The Joomla Web Services API has no login form and no session cookie. A program calls a REST endpoint and must prove who it is on every single request. That is the job of the api-authentication plugin group, which lives in plugins/api-authentication/ and answers the same onUserAuthenticate event in the API application.

Joomla ships two of these plugins:

PluginHow the request proves identity
API Authentication - Token A personal token sent in a request header. The recommended method.
API Authentication - Web Services Basic HTTP Basic Auth: username and password on every call.

Whichever plugin accepts the request, the resulting user keeps all their normal groups and permissions. An API user is never more powerful than the same user on the website.

Back to top

7. API Authentication - Basic

7.1 What It Does

The Web Services Basic plugin (plg_api-authentication_basic) lets a client send a username and password with each API request, using standard HTTP Basic authentication. Joomla reads the credentials, checks them against the #__users table with the same UserHelper::verifyPassword() call the login form uses, and returns success or failure.

curl -u myusername:mypassword \
     https://example.test/api/index.php/v1/users

7.2 Use It With Care

Basic authentication sends the real account password on every request. That has two consequences. First, you must only ever use it over HTTPS, or the password travels in the clear. Second, a script that stores a working password is a serious liability if it leaks. For most integrations the token plugin is the safer choice, because a token can be revoked without changing the user's password.

Back to top

8. API Authentication - Token

8.1 What It Does

The API Authentication - Token plugin (plg_api-authentication_token) is the recommended way to authenticate API calls. Each user gets a personal token, and the client sends it as a header on every request. No password ever leaves the server.

curl -H "Authorization: Bearer <your-token>" \
     https://example.test/api/index.php/v1/users

# Joomla also accepts the older header:
curl -H "X-Joomla-Token: <your-token>" \
     https://example.test/api/index.php/v1/users

8.2 How the Token Is Built

The token is not a random string the server stores and compares. It is a small, self-describing value that Joomla can verify with maths. When decoded, it holds three parts:

{algorithm} : {userId} : {HMAC}

Joomla keeps a per-user secret seed in the #__user_profiles table under the key joomlatoken.token. To verify a request, it recomputes an HMAC from that seed and the site's own secret, then compares it to the HMAC in the token. The comparison uses Crypt::timingSafeCompare(), a constant-time check that gives an attacker no timing clue. Only sha256 and sha512 are accepted as the algorithm.

8.3 Setting It Up

  1. Enable the User - Joomla API Token user plugin (it adds the token field) and the API Authentication - Token plugin (it reads the header).
  2. Open the user's profile in the backend, find the Joomla API Token tab, and generate a token.
  3. Copy the token once. Joomla stores only the seed, not the token itself, so you cannot read it again later.

A flag at joomlatoken.enabled in #__user_profiles lets you switch a user's token on or off. The plugin also refuses a token for any account that is blocked, not yet activated, or flagged to reset its password, returning STATUS_DENIED.

8.4 Which Users May Use a Token

By default only users in the Super Users group may authenticate with a token. You widen this through the allowedUserGroups parameter of the User - Joomla API Token plugin. Keep this list as narrow as the job allows.

Back to top

9. Under the Hood (Developer View)

9.1 The Tables Involved

TableRole
#__users Holds the username and the bcrypt password hash, plus block, activation, and requireReset flags.
#__user_keys Stores the Remember Me token and series for the Cookie plugin.
#__user_profiles Holds the API token seed (joomlatoken.token) and its enabled flag (joomlatoken.enabled).
#__extensions Where each plugin is enabled or disabled (enabled = 1 or 0).

9.2 The Authentication Service

The plugins do not run themselves. A small service class, Joomla\CMS\Authentication\Authentication, drives the whole chain. When the login form (or your own code) asks it to authenticate, it loads the enabled plugins, fires the event, collects every response, and returns the final verdict:

use Joomla\CMS\Authentication\Authentication;

$authentication = Authentication::getInstance();
$response       = $authentication->authenticate($credentials, $options);

if ($response->status === Authentication::STATUS_SUCCESS) {
    // the credentials are valid
}

You rarely call this yourself, because $app->login() does it for you. But knowing the class exists explains where the onUserAuthenticate event comes from and why the status constants live on it. Each enabled plugin is one strategy the service consults in turn.

9.3 The Shape of a Plugin

A modern authentication plugin is a small namespaced class. It declares the event it answers and does its work in a matching method:

namespace Joomla\Plugin\Authentication\Example\Extension;

use Joomla\CMS\Authentication\Authentication;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;

final class Example extends CMSPlugin implements SubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return ['onUserAuthenticate' => 'onUserAuthenticate'];
    }

    public function onUserAuthenticate($event): void
    {
        $credentials = $event->getArgument('credentials');
        $response    = $event->getAuthenticationResponse();

        if ($this->isValid($credentials)) {
            $response->status = Authentication::STATUS_SUCCESS;
            $response->email  = '...';
            $event->stopPropagation();
        } else {
            $response->status = Authentication::STATUS_FAILURE;
        }
    }
}

This same pattern is how real single sign-on integrations are built. An OAuth 2.0, SAML, Keycloak, Microsoft Entra ID, or Okta login is just an authentication plugin that checks the credentials against an external identity provider instead of #__users, then fills in the same response object. The core never changes.

When a plugin matches a user, it loads the full account with the modern UserFactoryInterface rather than the old static Factory::getUser(). Inside a plugin you reach it through $this->getUserFactory()->loadUserById($id):

$user = $this->getUserFactory()->loadUserById($id);

$response->email    = $user->email;
$response->fullname = $user->name;

The factory is injected through the plugin's service provider, so the code supports dependency injection, stays testable, and avoids static calls. This is the standard way to load a user in Joomla 6.

9.4 Why You Should Not Block on Failure

A well-behaved plugin sets STATUS_SUCCESS and calls stopPropagation() only when it is sure. On failure it should leave the door open for the next plugin by setting STATUS_FAILURE (or STATUS_UNKNOWN if the credentials are simply not its concern) and returning quietly. This is how the Joomla, Cookie, and LDAP plugins live together: each tries in turn, and the first confident yes wins.

9.5 The Folder Layout

plugins/authentication/joomla/
├─ services/provider.php              (DI container wiring)
├─ src/Extension/Joomla.php           (the plugin class)
└─ joomla.xml                         (manifest, parameters)

The api-authentication plugins follow the same layout under plugins/api-authentication/. The only real difference is the application they run in.

Back to top

10. SEO and Metadata

Authentication is a backend and machine concern, so it has no direct SEO value of its own. There is nothing here for a search engine to index, and there should not be. But two indirect points are worth keeping in mind.

First, never expose API tokens or Basic credentials in anything a crawler or a browser can reach, such as a public repository, a client-side script, or a URL query string. A leaked credential is far more damaging than a poor ranking. Second, serve every authenticated path over HTTPS. Search engines and browsers both flag insecure password and token traffic, and that trust signal protects the reputation of your whole domain.

Back to top

11. Common Mistakes and Pitfalls

11.1 Disabling the Authentication - Joomla Plugin

Symptom: nobody can log in with a username and password, anywhere.

Fix: re-enable the Authentication - Joomla plugin. If you are locked out of the backend, set enabled = 1 for it directly in the #__extensions table.

11.2 Remember Me Never Works

Symptom: the Remember Me checkbox shows on the frontend login, but nobody stays logged in.

Fix: enable the Authentication - Cookie plugin. The checkbox does nothing without it. Remember that it never applies to the backend by design.

11.3 Confusing the Two Plugin Groups

Symptom: you enable a plugin to fix API access, but the website login changes instead, or the other way round.

Fix: the authentication group serves the login form; the api-authentication group serves the Web Services API. Enable the plugin in the group that matches the problem. To see exactly which plugins are on in both groups at once, query the database:

SELECT name, folder, enabled FROM #__extensions
WHERE folder IN ('authentication', 'api-authentication');

11.4 The API Token Returns 401 Even Though It Is Correct

Symptom: a valid token is rejected with an unauthorised error.

Fix: check three things. The API Authentication - Token plugin must be enabled, the user must belong to a group listed in allowedUserGroups, and the account must not be blocked or pending activation.

11.5 LDAP Over a Plain Connection

Symptom: LDAP login works, but passwords travel unencrypted across the network.

Fix: set the encryption parameter to tls or ssl and confirm the server's certificate is trusted. Never run production LDAP with encryption set to none.

11.6 Using Basic Auth Where a Token Belongs

Symptom: an integration script stores a real account password to call the API.

Fix: switch to the token plugin. A token can be revoked on its own without forcing a password change, and it limits the damage if the script leaks.

Back to top

12. Best Practices

If you remember only a few things from this article, remember these:

  • Keep the Authentication - Joomla plugin enabled. It is the password check for the whole site.
  • Enable the Authentication - Cookie plugin only if you actually want Remember Me, and know it never covers the backend.
  • Use LDAP for shared corporate accounts, always over TLS or SSL, never over a plain connection.
  • Prefer API tokens over Basic authentication, and keep allowedUserGroups as narrow as possible.
  • Treat tokens and Basic credentials like passwords: HTTPS only, never in a repository or a browser script, revoke the moment they might leak.
  • Add new login methods (SSO, social, custom) with your own authentication plugin. Never patch the core.
  • On failure, let your plugin pass the question on; only stop the chain when you are sure of a success.
Back to top

13. Quick Reference

GROUPS          authentication       login form (HTML, session)
                api-authentication   Web Services API (REST, no session)

EVENT           onUserAuthenticate   answered by every auth plugin

LOGIN PLUGINS   Joomla   password check against #__users (keep enabled)
                Cookie   Remember Me, frontend only, #__user_keys
                LDAP     external directory, bind or search, use TLS

API PLUGINS     Token    Authorization: Bearer / X-Joomla-Token header
                Basic    curl -u user:pass  (HTTPS only)

STATUS          STATUS_SUCCESS  valid, continue
                STATUS_FAILURE  wrong credentials
                STATUS_DENIED   blocked / inactive / reset required
                STATUS_UNKNOWN  "not my job", try the next plugin

TABLES          #__users          username + bcrypt password hash
                #__user_keys      Remember Me token + series
                #__user_profiles  API token seed (joomlatoken.token)
                #__extensions     where each plugin is enabled

API SETUP       enable User - Joomla API Token + API Auth Token plugin
                generate token on the user profile, copy it once
                widen allowedUserGroups carefully

DIAGNOSE        SELECT name, folder, enabled FROM #__extensions
                WHERE folder IN ('authentication','api-authentication');
Back to top

14. Summary

Authentication in Joomla looks like a single password check, but it is a small system of cooperating plugins:

  • Two plugin groups share one event: authentication guards the login form, api-authentication guards the Web Services API.
  • Authentication - Joomla does the default bcrypt password check, re-hashes on login, and defends against timing attacks.
  • Authentication - Cookie powers Remember Me with a rotating token and series, on the frontend only.
  • Authentication - LDAP checks credentials against an external directory by binding to it.
  • API Token and Basic let programs authenticate without a form; the token method is safer because it can be revoked on its own.
  • The response object and its status constants are how every plugin says yes, no, or "not my job".

Once you see authentication as an event answered by a chain of plugins, the whole system becomes predictable. You know which plugin to enable, where each one stores its data, and where to look when a login or an API call is refused.

If your site suffers from logins that fail without a clear reason, Remember Me that will not stick, or an API token that keeps getting rejected, the cause is almost always one of the layers above. Tracing it back in the right order is exactly the kind of methodical work a Joomla specialist does first, and it is often the difference between a site you can trust and one you cannot.

Back to top
Authentication in Joomla
Peter Martin
Peter Martin

Joomla specialist and Linux admin for fast, secure and scalable websites.