-2

Am in the process of rewriting an existing PHP project to Symfony 5.3. I want to upgrade its authentication system to Symfony's. Only issue: Doctrine is not an option in this project.

How can I use Symfony's authentication (possibly together with the new Authenticator-based Security) without invoking Doctrine anywhere?

I know that I must implement a UserLoaderInterface, but the docs use Doctrine so heavily that I cannot figure out how to do it without.

The post I just mentioned is asking something similar, but it still uses Symfony 2 and is thus too outdated.

I have a database that has the necessary User table with the usual columns (ID, eMail, Password, Name, etc.).

To the point: How can I use Symfony's authentication (possibly together with the new Authenticator-based Security) without Doctrine?

Charles
  • 179
  • 1
  • 15
  • 3
    Start by running bin/console make:user and answer no when it asks if you want to use Doctrine. That will give you a Security\User class as well as an empty Security\UserProvider. security.yaml will be wired to use your provider. You just need to implement the database stuff yourself. make:auth should work as before. – Cerad Sep 03 '21 at 21:46
  • 1
    Hah. I just noticed I was the one who answered your stackoverflow link back in 2014. Some questions are just timeless. – Cerad Sep 03 '21 at 21:57
  • I went ahead with make:user. All went well. Is it correct that I must now add the necessary SQL to get the information according to the method in UserProvider.php? I have an idea what to put in loadUserByIdentifier() ( (I guess something like mentioned here: https://symfony.com/doc/current/security/user_provider.html#using-a-custom-query-to-load-the-user), but the others (e.g. refreshUser(), upgradePassword()) will be a challenge. – Charles Sep 04 '21 at 05:48
  • When using make:auth, it asks me which User class I want to authenticate. What do I say there? Is that the UserProvider? But I am not 100% on what to use make:auth for. I already have a login form. Is that to create the login/logout routes? Also: Thanks for the tip. Wasn't my question in 2014, but good to see you stick around. Just stands to show that the docs could do with some broadening since 2014. ;-) – Charles Sep 04 '21 at 07:19
  • What the make:auth ends up doing is creating an Authenticator class which contains most of the configuration needs to authenticate. Select the Login form authenticator option and enter App\Security\User for your user class assuming you used the defaults for make:user. As an alternative to make:auth you can manually add a form_login section to your firewall in security.yaml per the docs. But I would say make:auth is the recommended approach. You can tweak the generated code to use your login form. Try it both ways if you want. – Cerad Sep 04 '21 at 12:44
  • As far as the UserProvider goes, the docs talk quite a bit about it. Yes you need to query the database in the loadUserByIdentifier. refreshUser is called during each request after a successful login with the session user. You will have to decide what, if anything, you will want to do here. I'd actually suggest starting a second Symfony app and use the Doctrine ORM just to see what the default stuff looks like. Authentication is a big topic. And as much as I hate to say it, it's mostly a question of reading and understanding the docs. – Cerad Sep 04 '21 at 12:51
  • Thanks for the tips. Will try my best. I think I have read the docs on this topic a couple of times already. Starting out with Symfony is not for the faint of heart, but I'm getting there. Thanks for your pointers. Maybe one day someone will write up a how-to on this topic. :-) – Charles Sep 04 '21 at 13:54
  • Good luck. If you have more big picture type questions, consider posting on the [Symfony Discussion](https://github.com/symfony/symfony/discussions) board. – Cerad Sep 04 '21 at 14:18
  • Noted. Will also look for a more beginner-oriented discussion forum. :-) – Charles Sep 04 '21 at 15:07

1 Answers1

-1

To configurate that is on the the official website and also on this tutorial in SymfonyCast, but basically you can authenticate the user as you want:

See the next example:

Create a file on src\App\Security folder if your configuration is using the default config and create the class TokenAuthenticator, now see the below code, in this case check the class App\Service\ExternalAuthenticator, who will be in charge to get the information from other service or api and the return.

<?php

namespace App\Security;

use App\Example\Student;
use App\Service\ExternalAuthenticator;
use App\DTO\INFORMATIONFROMOTHERSERVICE;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Security;

final class TokenAuthenticator extends AbstractGuardAuthenticator
{
    /** @var Security */
    private $security;

    /** @var ExternalAuthenticator */
    private $externalAuthenticator;

    /** @var UrlGeneratorInterface */
    private $urlGenerator;

    public function __construct(
        Security $security,
        ExternalAuthenticator $externalAuthenticator
    ) {
        $this->security = $security;
        $this->externalAuthenticator = $externalAuthenticator;
    }

    /**
     * {@inheritDoc}
     */
    public function supports(Request $request)
    {
        //on this example, this guard must be using if on the request contains the word token
        $response = false;
        $apiKey = $request->query->get('token');
        if (!is_null($apiKey)) {
            $response = true;
        }

        return $response;
    }

    /**
     * {@inheritDoc}
     */
    public function getCredentials(Request $request)
    {
        $apiKey = $request->query->get('token');
        // Validate with anything you want, other service or api
        /** @var INFORMATIONFROMOTHERSERVICE**/
        $dtoToken = $this->externalAuthenticator->validateToken($apiKey, $simulator);

        return $dtoToken;
    }

    /**
     * @param INFORMATIONFROMOTHERSERVICE $credentials
     * @param UserProviderInterface $userProvider
     * @return INFORMATIONFROMOTHERSERVICE |UserInterface|null
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        return $userProvider;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return true;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new RedirectResponse($this->urlGenerator->generate('home_incorrect'));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
    {
        return new RedirectResponse($request->getPathInfo());
    }

    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->urlGenerator->generate('home_incorrect'));
    }

    public function supportsRememberMe()
    {
        // todo
    }
}

Now the external service must return App\DTO\INFORMATIONFROMOTHERSERVICE class, but this class must implement the UserInterface, now with this in mind. We need to configurate what guard must be in charge of what routes, see the next example:

security:
    encoders:
        App\Entity\User:
            algorithm: bcrypt

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        //You can use a 
        custom_provider:
            id : App\DTO\INFORMATIONFROMOTHERSERVICE
        # used to reload user from session & other features (e.g. switch_user)
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        survey:
            anonymous: true
            pattern: ^/(custom_path)/
            // The
            provider: custom_provider
            guard:
                // You can use as many authenticator that you want, but in the node entrypoint, you must choose who must be the default if only is one you could remove the entrypoint node, similar as the main firewall
                authenticators:
                    - App\Security\TokenAuthenticator
                    - App\Security\OtherAuthenticator
                entry_point: App\Security\OtherAuthenticator
        main:
            anonymous: true
            lazy: true
            provider: app_user_provider
            logout:
                path: app_logout
            guard:
                authenticators:
                    - App\Security\AppAuthenticator

Also see the next documentation, that will guide you to create the class App\DTO\INFORMATIONFROMOTHERSERVICE.

I hope this answer, help you