Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security] Update custom authenticator docs to include identifier normalization #20636

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions security/custom_authenticator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,20 @@ requires a user and some sort of "credentials" (e.g. a password).
Use the
:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge`
to attach the user to the passport. The ``UserBadge`` requires a user
identifier (e.g. the username or email), which is used to load the user
using :ref:`the user provider <security-user-providers>`::
identifier (e.g. the username or email)::

use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

// ...
$passport = new Passport(new UserBadge($email), $credentials);
$passport = new Passport(new UserBadge($userIdentifier), $credentials);

User Identifier
~~~~~~~~~~~~~~~

The user identifier is a unique string that identifies the user. It is used
to load the user using :ref:`the user provider <security-user-providers>`.
This identifier is often something like the user's email address or username,
but it could be any unique value associated with the user.

.. note::

Expand Down Expand Up @@ -255,6 +262,89 @@ using :ref:`the user provider <security-user-providers>`::
}
}

It is a good practice to normalize the user identifier before using it.
For example, this ensures that variations such as "john.doe", "John.Doe",
or "JOHN.DOE" refer to the same user.
Normalization can include converting the identifier to lowercase
and trimming unnecessary spaces.
You can optionally pass a user identifier normalizer as third argument to the
``UserBadge``. This callable receives the ``$userIdentifier``
and must return a normalized user identifier as a string.

.. versionadded:: 7.3
Comment on lines +270 to +274
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New section is not correctly rendered:

image

I don't know how to fix it


The support of the user identifier normalizer was introduced in Symfony 7.3.


// src/Security/NormalizedUserBadge.php
namespace App\Security;

use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\String\UnicodeString;
use function Symfony\Component\String\u;

final class NormalizedUserBadge extends UserBadge
{
public function __construct(string $identifier)
{
$callback = static fn (string $identifier) => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString();

parent::__construct($identifier, null, $callback);
}
}

// src/Security/PasswordAuthenticator.php
namespace App\Security;

final class PasswordAuthenticator extends AbstractLoginFormAuthenticator
{
// Simplified for brievety
public function authenticate(Request $request): Passport
{
$username = (string) $request->request->get('username', '');
$password = (string) $request->request->get('password', '');

$request->getSession()
->set(SecurityRequestAttributes::LAST_USERNAME, $username);

return new Passport(
new NormalizedUserBadge($username),
new PasswordCredentials($password),
[
//All other useful badges
]
);
}
}

.. note::

Similarly, Google normalizes email addresses so that "john.doe", "j.hon.d.oe",
and "johndoe" all correspond to the same account.
This involves removing dots and converting the email address to lowercase
(though normalization specifics depend on your use case).
Comment on lines +320 to +325
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the other note below, I'm not sure this one is needed


.. note::

In enterprise applications, a user may need to use their identifier in multiple formats,
such as:
- ``[email protected]``
- ``acme.com\jdoe``
- ``https://acme.com/+jdoe``
- ``acct:[email protected]``

Normalizing these identifiers (e.g., converting to lowercase, trimming spaces,
or unifying formats) simplifies searches and ensures that the same user identity
is consistently recognized.
This is particularly useful to avoid duplicates caused by format variations.

User Credential
~~~~~~~~~~~~~~~

The user credential is used to authenticate the user i.e. to verify
the validity of the provided information (such as a password, an API token,
or other custom credentials).

The following credential classes are supported by default:

:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials`
Expand Down