Authentication in Joomla
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 group | Folder | Used 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.
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:
| Property | Meaning |
|---|---|
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:
| Constant | Meaning |
|---|---|
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.
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.
4. Authentication - Cookie (Remember Me)
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_keysby 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
| Parameter | Default | Meaning |
|---|---|---|
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 top5. 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:
| Method | How 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
| Parameter | Purpose |
|---|---|
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.
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:
| Plugin | How 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 top7. 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 top8. 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
- Enable the User - Joomla API Token user plugin (it adds the token field) and the API Authentication - Token plugin (it reads the header).
- Open the user's profile in the backend, find the Joomla API Token tab, and generate a token.
- 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.
9. Under the Hood (Developer View)
9.1 The Tables Involved
| Table | Role |
|---|---|
#__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.
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 top11. 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 top12. 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
allowedUserGroupsas 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.
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 top14. 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:
authenticationguards the login form,api-authenticationguards 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

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


