How to Write a Custom Authenticator

Symfony comes with many authenticators and third party bundles also implement more complex cases like JWT and oAuth 2.0. However, sometimes you need to implement a custom authentication mechanism that doesn't exist yet or you need to customize one. In such cases, you must create and use your own authenticator.

Symfony には多くのオーセンティケーターが付属しており、サードパーティのバンドルも JWT や oAuth2.0 などのより複雑なケースを実装しています。ただし、まだ存在しないカスタム認証メカニズムを実装する必要がある場合や、カスタマイズする必要がある場合があります。このような場合、独自のオーセンティケーターを作成して使用する必要があります。

Authenticators should implement the AuthenticatorInterface. You can also extend AbstractAuthenticator, which has a default implementation for the createToken() method that fits most use-cases:

オーセンティケーターは、AuthenticatorInterface を実装する必要があります。AbstractAuthenticator を拡張することもできます。これには、ほとんどのユースケースに適合する createToken() メソッドのデフォルトの実装があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// src/Security/ApiKeyAuthenticator.php
namespace App\Security;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class ApiKeyAuthenticator extends AbstractAuthenticator
{
    /**
     * Called on every request to decide if this authenticator should be
     * used for the request. Returning `false` will cause this authenticator
     * to be skipped.
     */
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('X-AUTH-TOKEN');
    }

    public function authenticate(Request $request): Passport
    {
        $apiToken = $request->headers->get('X-AUTH-TOKEN');
        if (null === $apiToken) {
            // The token header was empty, authentication fails with HTTP Status
            // Code 401 "Unauthorized"
            throw new CustomUserMessageAuthenticationException('No API token provided');
        }

        return new SelfValidatingPassport(new UserBadge($apiToken));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // on success, let the request continue
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $data = [
            // you may want to customize or obfuscate the message first
            'message' => strtr($exception->getMessageKey(), $exception->getMessageData())

            // or to translate this message
            // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
        ];

        return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
    }
}

Tip

ヒント

If your custom authenticator is a login form, you can extend from the AbstractLoginFormAuthenticator class instead to make your job easier.

カスタム オーセンティケーターがログイン フォームの場合、代わりに、AbstractLoginFormAuthenticator クラスから拡張して、作業を簡単にすることができます。

The authenticator can be enabled using the custom_authenticators setting:

オーセンティケーターは、custom_authenticators 設定を使用して有効にすることができます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:

    # ...
    firewalls:
        main:
            custom_authenticators:
                - App\Security\ApiKeyAuthenticator

Tip

ヒント

You may want your authenticator to implement AuthenticationEntryPointInterface. This defines the response sent to users to start authentication (e.g. when they visit a protected page). Read more about it in The Entry Point: Helping Users Start Authentication.

オーセンティケータにAuthenticationEntryPointInterfaceを実装することができます。これは、認証を開始するためにユーザーに送信される応答を定義します (たとえば、保護されたページにアクセスしたとき)。詳細については、「エントリ ポイント: ユーザーが認証を開始できるようにする」を参照してください。

The authenticate() method is the most important method of the authenticator. Its job is to extract credentials (e.g. username & password, or API tokens) from the Request object and transform these into a security Passport (security passports are explained later in this article).

authenticate() メソッドは、オーセンティケータの最も重要なメソッドです。その仕事は、Request オブジェクトから資格情報 (ユーザー名とパスワード、または API トークンなど) を抽出し、これらを securityPassport に変換することです (セキュリティ パスポートについては、この記事の後半で説明します)。

After the authentication process finished, the user is either authenticated or there was something wrong (e.g. incorrect password). The authenticator can define what happens in these cases:

認証プロセスが終了した後、ユーザーは認証されているか、何か問題がありました (パスワードが間違っているなど)。オーセンティケーターは、次の場合に何が起こるかを定義できます。
onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response

If the user is authenticated, this method is called with the authenticated $token. This method can return a response (e.g. redirect the user to the homepage).

ユーザーが認証されている場合、このメソッドは認証された $token で呼び出されます。このメソッドは、応答を返すことができます (例: ユーザーをホームページにリダイレクトします)。

If null is returned, the request continues like normal (i.e. the controller matching the login route is called). This is useful for API routes where each route is protected by an API key header.

null が返された場合、リクエストは通常​​どおり続行されます (つまり、ログイン ルートに一致するコントローラーが呼び出されます)。これは、各ルートが API キー ヘッダーによって保護されている APIroute に役立ちます。
onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response

If an AuthenticationException is thrown during authentication, the process fails and this method is called. This method can return a response (e.g. to return a 401 Unauthorized response in API routes).

認証中に AuthenticationException がスローされた場合、プロセスは失敗し、このメソッドが呼び出されます。このメソッドは応答を返すことができます (たとえば、API ルートで 401 Unauthorized 応答を返すため)。

If null is returned, the request continues like normal. This is useful for e.g. login forms, where the login controller is run again with the login errors.

null が返された場合、リクエストは通常​​どおり続行されます。これは、たとえば次の場合に役立ちます。ログイン フォームでは、ログイン コントローラがログイン エラーで再度実行されます。

If you're using login throttling, you can check if $exception is an instance of TooManyLoginAttemptsAuthenticationException (e.g. to display an appropriate message).

ログインスロットリングを使用している場合は、$exception が TooManyLoginAttemptsAuthenticationException のインスタンスであるかどうかを確認できます (たとえば、適切なメッセージを表示するため)。

Caution: Never use $exception->getMessage() for AuthenticationException instances. This message might contain sensitive information that you don't want to be publicly exposed. Instead, use $exception->getMessageKey() and $exception->getMessageData() like shown in the full example above. Use CustomUserMessageAuthenticationException if you want to set custom error messages.

注意: AuthenticationException インスタンスに $exception->getMessage() を使用しないでください。このメッセージには、公開したくない機密情報が含まれている可能性があります。代わりに、上記の完全な例に示されているように、$exception->getMessageKey() と $exception->getMessageData() を使用してください。カスタム エラー メッセージを設定する場合は、CustomUserMessageAuthenticationException を使用します。

Tip

ヒント

If your login method is interactive, which means that the user actively logged into your application, you may want your authenticator to implement the InteractiveAuthenticatorInterface so that it dispatches an InteractiveLoginEvent

ログイン方法がインタラクティブである場合、つまりユーザーがアプリケーションにアクティブにログインした場合、オーセンティケーターに InteractiveAuthenticatorInterface を実装して、InteractiveLoginEvent をディスパッチすることができます。

Security Passports

A passport is an object that contains the user that will be authenticated as well as other pieces of information, like whether a password should be checked or if "remember me" functionality should be enabled.

The default Passport requires a user and some sort of "credentials" (e.g. a password).

defaultPassport には、ユーザーと何らかの「資格情報」(パスワードなど) が必要です。

Use the 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 the user provider:

UserBadge を使用して、ユーザーをパスポートに添付します。 UserBadge にはユーザー識別子 (ユーザー名や電子メールなど) が必要です。これは、ユーザー プロバイダーを使用してユーザーを読み込むために使用されます。
1
2
3
4
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

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

Note

ノート

The maximum length allowed for the user identifier is 4096 characters to prevent session storage flooding attacks.

セッション ストレージ フラッディング攻撃を防ぐため、ユーザー識別子に許可される最大長は 4096 文字です。

Note

ノート

You can optionally pass a user loader as second argument to the UserBadge. This callable receives the $userIdentifier and must return a UserInterface object (otherwise a UserNotFoundException is thrown):

オプションで、2 番目の引数としてユーザー ローダーを theUserBadge に渡すことができます。この callable は $userIdentifier を受け取り、UserInterface オブジェクトを返す必要があります (そうでない場合、UserNotFoundException がスローされます)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Security/CustomAuthenticator.php
namespace App\Security;

use App\Repository\UserRepository;
// ...

class CustomAuthenticator extends AbstractAuthenticator
{
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function authenticate(Request $request): Passport
    {
        // ...

        return new Passport(
            new UserBadge($email, function (string $userIdentifier) {
                return $this->userRepository->findOneBy(['email' => $userIdentifier]);
            }),
            $credentials
        );
    }
}

The following credential classes are supported by default:

デフォルトでは、次の資格情報クラスがサポートされています。
PasswordCredentials

This requires a plaintext $password, which is validated using the password encoder configured for the user:

これには、プレーンテキストの $password が必要です。これは、ユーザー用に構成されたパスワード エンコーダーを使用して検証されます。
1
2
3
4
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;

// ...
return new Passport(new UserBadge($email), new PasswordCredentials($plaintextPassword));
CustomCredentials

Allows a custom closure to check credentials:

カスタム クロージャが資格情報をチェックできるようにします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;

// ...
return new Passport(new UserBadge($email), new CustomCredentials(
    // If this function returns anything else than `true`, the credentials
    // are marked as invalid.
    // The $credentials parameter is equal to the next argument of this class
    function ($credentials, UserInterface $user) {
        return $user->getApiToken() === $credentials;
    },

    // The custom credentials
    $apiToken
));

Self Validating Passport

If you don't need any credentials to be checked (e.g. when using API tokens), you can use the SelfValidatingPassport. This class only requires a UserBadge object and optionally Passport Badges.

認証情報を確認する必要がない場合 (API トークンを使用する場合など) は、SelfValidatingPassport を使用できます。このクラスには、UserBadge オブジェクトと、オプションでパスポート バッジのみが必要です。

Passport Badges

The Passport also optionally allows you to add security badges. Badges attach more data to the passport (to extend security). By default, the following badges are supported:

Passport では、オプションでセキュリティ バッジを追加することもできます。バッジは、(セキュリティを拡張するために) より多くのデータをパスポートに添付します。デフォルトでは、次のバッジがサポートされています。
RememberMeBadge
When this badge is added to the passport, the authenticator indicates remember me is supported. Whether remember me is actually used depends on special remember_me configuration. Read How to Add "Remember Me" Login Functionality for more information.
このバッジがパスポートに追加されると、オーセンティケーターは、remember me がサポートされていることを示します。 remember me が実際に使用されるかどうかは、特別な remember_me 構成に依存します。詳細については、「Remember Me」ログイン機能を追加する方法を参照してください。
PasswordUpgradeBadge
This is used to automatically upgrade the password to a new hash upon successful login (if needed). This badge requires the plaintext password and a password upgrader (e.g. the user repository). See Password Hashing and Verification.
これは、ログインの成功時にパスワードを新しいハッシュに自動的にアップグレードするために使用されます (必要な場合)。このバッジには、プレーンテキストのパスワードとパスワード アップグレーダー (ユーザー リポジトリなど) が必要です。パスワードのハッシュと検証を参照してください。
CsrfTokenBadge
Automatically validates CSRF tokens for this authenticator during authentication. The constructor requires a token ID (unique per form) and CSRF token (unique per request). See How to Implement CSRF Protection.
認証中に、このオーセンティケーターの CSRF トークンを自動的に検証します。コンストラクターには、トークン ID (フォームごとに固有) と CSRF トークン (要求ごとに固有) が必要です。 CSRF保護を実装する方法を参照してください。
PreAuthenticatedUserBadge
Indicates that this user was pre-authenticated (i.e. before Symfony was initiated). This skips the pre-authentication user checker.
このユーザーが事前に認証されたことを示します (つまり、Symfony が開始される前)。これにより、認証前のユーザー チェッカーがスキップされます。

Note

ノート

The PasswordUpgradeBadge is automatically added to the passport if the passport has PasswordCredentials.

パスポートに PasswordCredentials がある場合、PasswordUpgradeBadge はパスポートに自動的に追加されます。

For instance, if you want to add CSRF to your custom authenticator, you would initialize the passport like this:

たとえば、カスタム認証システムに CSRF を追加する場合は、次のようにパスポートを初期化します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Service/LoginAuthenticator.php
namespace App\Service;

// ...
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

class LoginAuthenticator extends AbstractAuthenticator
{
    public function authenticate(Request $request): Passport
    {
        $password = $request->request->get('password');
        $username = $request->request->get('username');
        $csrfToken = $request->request->get('csrf_token');

        // ... validate no parameter is empty

        return new Passport(
            new UserBadge($username),
            new PasswordCredentials($password),
            [new CsrfTokenBadge('login', $csrfToken)]
        );
    }
}

Passport Attributes

Besides badges, passports can define attributes, which allows the authenticate() method to store arbitrary information in the passport to access it from other authenticator methods (e.g. createToken()):

バッジに加えて、パスポートは属性を定義できます。これにより、authenticate() メソッドが任意の情報をパスポートに保存して、他の認証メソッド (例: createToken()) からアクセスできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ...
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

class LoginAuthenticator extends AbstractAuthenticator
{
    // ...

    public function authenticate(Request $request): Passport
    {
        // ... process the request

        $passport = new SelfValidatingPassport(new UserBadge($username), []);

        // set a custom attribute (e.g. scope)
        $passport->setAttribute('scope', $oauthScope);

        return $passport;
    }

    public function createToken(Passport $passport, string $firewallName): TokenInterface
    {
        // read the attribute value
        return new CustomOauthToken($passport->getUser(), $passport->getAttribute('scope'));
    }
}