I am working on a new Symfony 5.3.6 project and want to implement authentication, based on the new system as stated in:
https://symfony.com/doc/current/security/authenticator_manager.html#creating-a-custom-authenticator
I do not have any users and just want to check if the sent api token is correct, so when implementing this method:
public function authenticate(Request $request): PassportInterface
{
$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));
}
where exactly is the checking done? Have i forgotten to implement another Class somewhere?
If I leave the code as is it lands directly in onAuthenticationFailure.
I understand, that I could implement Users/UserProvider with an attribute $apiToken and then the system would check if the database entry corresponds with the token in the request. But i do not have users.
It should be possible without having users, because on the above URL, it says:
Self Validating Passport
If you don’t need any credentials to be checked (e.g. when using API tokens), you can use the Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport. This class only requires a UserBadge object and optionally Passport Badges.
But that is a little thin. How do I "use" it?
You can use next validation
return new SelfValidatingPassport(
new UserBadge($apiToken, function() use ($apiToken) {
// TODO: here you can implement any check
})
);
Ok, I think I got the point, in any case, you need to handle some User & then you need to create a customer Userprovider.
Here my logic:
App\Security\UserProvider:
class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
public function loadUserByIdentifier($identifier): UserInterface
{
if ($identifier === 'YOUR_API_KEY') {
return new User();
}
throw new UserNotFoundException('API Key is not correct');
}
...
App\Security\ApiKeyAuthenticator:
class ApiKeyAuthenticator extends AbstractAuthenticator
{
private UserProvider $userProvider;
public function __construct(UserProvider $userProvider)
{
$this->userProvider = $userProvider;
}
public function supports(Request $request): ?bool
{
// allow api docs page
return trim($request->getPathInfo(), '/') !== 'docs';
}
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-API-KEY');
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, function () use ($apiToken) {
return $this->userProvider->loadUserByIdentifier($apiToken);
})
);
}
It works for me, my API is protected by a basic API Key in the header. I don't know if it's the best way, but seems ok.
And define in your security.yaml:
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
id: App\Security\UserProvider