Skip to content

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

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

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from

Conversation

Spomky
Copy link
Contributor

@Spomky Spomky commented Feb 7, 2025

Fixes #20632

Improve the documentation for custom authenticators by detailing the concept of user identifier normalization. Introduced the NormalizedUserBadge example to demonstrate how to simplify and standardize identifiers.

@Spomky Spomky force-pushed the features/userid-normalizer branch from b58b83b to a7df8a2 Compare February 7, 2025 21:35
Comment on lines +270 to +274
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

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Many thanks.
I will take time to update this section.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be fixed with the last commit

Comment on lines 320 to 327
.. 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).
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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe both notes provide valuable context and would prefer to keep tthem:

  • The first shows a well-known real-world example (Google).
  • The second one highlights enterprise-specific formats where normalization is important.

@Spomky Spomky force-pushed the features/userid-normalizer branch from a7df8a2 to bf84645 Compare May 8, 2025 11:50
Added support for normalizing user identifiers in Symfony 7.3 to ensure consistent comparison and storage. Updated documentation with implementation examples, highlighting best practices like converting identifiers to lowercase and handling various formats. This improvement reduces the risk of duplicates and improves reliability in user authentication.
@Spomky Spomky force-pushed the features/userid-normalizer branch from bf84645 to ac6a258 Compare May 8, 2025 11:54
Comment on lines +278 to +322
For instance, the example below uses a normalizer that converts usernames to a normalized, ASCII-only, lowercase format,
suitable for consistent comparison and storage.

// 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::
Copy link
Contributor

@alamirault alamirault May 9, 2025

Choose a reason for hiding this comment

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

  • Php code was not rendered. I added :: and .. code-block:: php (Not 100% sure it's the best way)
  • I replace (string) $request->request->get() by $request->request->getString
Suggested change
For instance, the example below uses a normalizer that converts usernames to a normalized, ASCII-only, lowercase format,
suitable for consistent comparison and storage.
// 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::
For instance, the example below uses a normalizer that converts usernames to a normalized, ASCII-only, lowercase format,
suitable for consistent comparison and storage::
// 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);
}
}
.. code-block:: php
// src/Security/PasswordAuthenticator.php
namespace App\Security;
final class PasswordAuthenticator extends AbstractLoginFormAuthenticator
{
// Simplified for brievety
public function authenticate(Request $request): Passport
{
$username = $request->request->getString('username');
$password = $request->request->getString('password');
$request->getSession()
->set(SecurityRequestAttributes::LAST_USERNAME, $username);
return new Passport(
new NormalizedUserBadge($username),
new PasswordCredentials($password),
[
//All other useful badges
]
);
}
}
.. note::

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Security] Add a normalization step for the user-identifier in firewalls
4 participants