Title Text

LOGIN

Jeden login vládne všem

Symfony aplikacím

Jsem Tomáš :)

programátor

školitel

rodič

Apicart

Jak problém vznikl?

Nákupní košík

e-mail

heslo

Přihlásit

 - vlastní databáze

 - vlastní aplikace

 - vlastní logika

 - vlastní subdoména

 - samostatný produkt

 - vlastní autentizace

Máme další produkt...

Vyhledávání

e-mail

heslo

Přihlásit

 - vlastní databáze

 - vlastní aplikace

 - vlastní logika

 - vlastní subdoména

 - samostatný produkt

 - vlastní autentizace

Jaký je problém?

Nákupní košík

e-mail

heslo

Přihlásit

Vyhledávání

e-mail

heslo

Přihlásit

...

e-mail

heslo

Přihlásit

Jaké je řešení?

Nákupní košík

Vyhledávání

...

Accounts

e-mail

heslo

Přihlásit

Jak na to?

 - zrefaktorujte autentizaci/registraci do nové aplikace

 - vytvořte API pro získání informací o přihlášeném uživateli

 - vytvořte si privátní PHP balíček pro composer

 - zvolte si jedno centrální úložiště pro session

 - nastavte si správně cookie domain

 - implementujte redirecty z a do aplikací

 - obrňte se trpělivostí... ;)

Redis pro sessions

framework:
    session:
        cookie_domain: .apicart.net
        cookie_lifetime: 1209600 # 14 days
        gc_maxlifetime: 1209600 # 14 days
        cookie_samesite: lax
        cookie_secure: auto
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
        name: PHPSESSID

services:
    SessionRedis:
        class: Redis
        calls:
            -   method: connect
                arguments:
                    - '%env(SESSION_REDIS_HOST)%'
                    - '%env(int:SESSION_REDIS_PORT)%'
            -   method: select
                arguments:
                    - '%env(int:SESSION_REDIS_DATABASE)%'

    Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler:
        arguments:
            - '@SessionRedis'

Symfony security

security:
    providers:
        apicart_accounts_provider:
            id: Apicart\Accounts\Security\UserProvider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            pattern: ^/
            provider: apicart_accounts_provider
            user_checker: Apicart\Accounts\Security\UserChecker
            anonymous: ~

UserProvider

final class UserProvider implements UserProviderInterface
{
    private $userApi;

    public function __construct(UserApi $userApi)
    {
        $this->userApi = $userApi;
    }

    public function loadUserByUsername($username)
    {
        $user = $this->userApi->findByUsername($username);
        if ($user === null) {
            throw new UsernameNotFoundException;
        }
        return $user;
    }

    public function refreshUser(UserInterface $user)
    { /** ... **/ }

    public function supportsClass($class)
    { /** ... **/ }
}

User

class User implements UserInterface, Serializable
{

    //...

    public function serialize(): string
    {
        return serialize([
            $this->username,
            $this->password,
            $this->salt,
        ]);
    }
    
    public function unserialize($serialized): void
    {
        [
            $this->username,
            $this->password,
            $this->salt,
        ] = unserialize($serialized, ['allowed_classes' => false]);
    }

}

AbstractToken::hasUserChanged

private function hasUserChanged(UserInterface $user)
{

    if ($this->user instanceof EquatableInterface) {
        return !(bool) $this->user->isEqualTo($user);
    }

    if ($this->user->getPassword() !== $user->getPassword()) {
        return true;
    }

    if ($this->user->getSalt() !== $user->getSalt()) {
        return true;
    }

    if ($this->user->getUsername() !== $user->getUsername()) {
        return true;
    }

    // AdvancedUserInterface is deprecated since Symfony 4.1

    return false;
}

UserChecker

final class UserChecker implements UserCheckerInterface
{
    public function checkPreAuth(UserInterface $user): void
    { /** ... **/ }


    public function checkPostAuth(UserInterface $user): void
    {
        if (! $user instanceof User) {
            return;
        }

        if (! $user->isActive()) {
            throw new AccountDeactivatedException;
        }
    }
}

Redirect z Controlleru

trait AccountsRedirectTrait 
{
    private $host = 'https://accounts.apicart.net';

    public function redirectToLogin(): RedirectResponse
    {
        $request = $this->getRequest();

        return new RedirectResponse(
            $this->host . '?backlink=' . $this->getBacklink($request)
        );
    }

    public function redirectToLogout(): RedirectResponse
    {
        $request = $this->getRequest();

        return new RedirectResponse(
            $this->host . '/logout?backlink=' . $this->getBacklink($request)
        );
    }

    private function getBacklink(Request $request): string
    {
        return $this->urlGenerator->generate(
            $request->get('_route'),
            $request->get('_route_params'),
            UrlGeneratorInterface::ABSOLUTE_URL
        );
    }
}

Finální struktura

Nákupní košík

Vyhledávání

...

Accounts

e-mail

heslo

Přihlásit

Shrnutí...

+ máme jednotný login do všech aplikací

 máme jednotný login do všech aplikací

Co dál?

- zbavit se sdílení session mezi aplikacemi

- implementovat dvoufázové ověření

- implementovat přepínání účtů

Díky za pozornost!

Máte otázky?

Tomáš Pilař

                  https://www.tomaspilar.cz/

                              https://twitter.com/TomasPilaru

https://apicart.net/

 

Apicart

Made with Slides.com