Symfony 4 | User Management

In meinen beiden vorherigen Beiträgen habe ich über die Erstellung von Menüs, Internationalisierung und dem aufsetzen von Symfony gesprochen. Nun ist es an der Zeit das erste Usermanagement anzugehen.

Die Rollen

Macht euch vorher klar was Ihr jetzt und in einem Jahr benötigt. Lieber einen Schritt zu weit gedacht, als später alles umzukrempeln. In meinem Fall benötige ich 3 verschiedene Rollen (abgesehen vom normalen Seitenbesucher)

Admin

Hat alle Rechte für CRUD Operationen auf alle Entitäten

Bearbeiter

Hat CRU Rechte auf einige Entitäten

Helfer

Kann lediglich Aufträge einsehen und sich dafür eintragen

User Entität

Ich habe wie im ersten Artikel beschrieben mein User Model 1zu1 übernommen, dass heißt diesen Schritt spare ich mir. Ich habe mir lediglich einen User Provider in meiner security.yaml angelegt – in diesen definiere ich welche Entität genommen werden soll und welches Feld zur Identifikation genommen werden soll.

security:
    encoders:
        App\Entity\Users:
            algorithm: bcrypt
    providers:
        app_user_provider:
            entity:
                class: App\Entity\Users
                property: email

Symfony empfiehlt hier als Encription argon2i (damit braucht Ihr aber PHP 7.2 – also aufpassen) – ich nutze bcrypt (aus oben bereits genannten Gründen, ziehe das aber noch nach)

php bin/console make:auth

Das generiert euch folgende Files:

 created: src/Security/LoginFormAuthenticator.php
 updated: config/packages/security.yaml
 created: src/Controller/SecurityController.php
 created: templates/security/login.html.twig

und fügt der security.yaml folgende Zeilen hinzu:

guard:
    authenticators:
        - App\Security\LoginFormAuthenticator

Bestimmt so also euren „Türsteher“ der bestimmt wer rein kann und wer nicht 😉 Vor dieser Code-Generierung hat das ganze so ausgesehen

Fehlende Autorisation

Nun werdet Ihr automatisch auf die Login-Action des Security-Controllers geleitet (schön sieht es noch nicht aus, aber das kommt später)

Login Action

UserInterface

Da wir unsere User Entität nicht über make erstellt haben, nutzen wir von Haus aus kein UserInterface. Also fügen wir das noch hinzu:

use Symfony\Component\Security\Core\User\UserInterface;

/**
 * Users
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 */
class Users implements UserInterface...

Das Interface braucht 3 Methoden (getSalt(), eraseCredentials(), getRoles()) – vergesst diese nicht. Jede vernünftige IDE wird euch das aber anzeigen

getsalt() brauchen wir bei bcrypt nicht wenn wir es in der security.yaml implementieren erasecredentials() auch erstmal nicht da wir keine sensitiven Daten auf den Benutzer schreiben. Nur die getRoles()

/**
 * @see UserInterface
 */
public function getRoles(): array
{
    $roles = $this->role;
    // damit mindestens eine Rolle gesetzt wird
    $roles[] = 'ROLE_USER';

    return array_unique($roles);
}

Rollen Datentyp

In meiner alten Datenbank habe ich als Format einen einfachen String gehabt, hier möchte ich nun auf den von Symfony empfohlenen Weg umsteigen – JSON.

/**
* @ORM\Column(type="json")
*/
private $role = [];

Damit können wir später entspannter auf Multirollen switchen und man hält sich an einen Standard (bin kein Fan von Komma oder Semicolon getrennten Values wenn Mysql doch schon JSON als Typ mitbringt)

Das ganze sieht dann so in der Datenbank aus

{"role":"ROLE_USER"}

Login

Beim Login gibt es nicht all zu viel zu beachten, lediglich Style-technisch kann man hier etwas nachhelfen.

    <section id="login">
        <div class="container">
            <div class="row">
                <div class="col-12 col-md-6">

FORM ELEMENT

                </div>
            </div>
        </div>
    </section>

Je nachdem ob Ihr auch Bootstrap nutzt oder nicht. Da da Login-Formular relativ klein ist ist ein min-height hier angebracht.

#login{
  min-height: 70vh;
}
Login Page

Nun wäre es noch schön den normalen Benutzer und den Admin auf eine bestimmte Seite zu leiten. Das machen wir in der src/Security/LoginFormAuthenticator.php -> onAuthenticationSuccess() Methode.

Über return gebt Ihr die entsprechende Route zurück

return new RedirectResponse($this->router->generate('users_index'));

wollt Ihr aber wie ich hier versch. Pfade für versch. Rollen zurückgeben schaut euch mein GIST an.

Access Denied

Wenn Ihr aktuell eine Ressource aufruft, kommt im Debug Mode hier erstmal folgende Meldung

Access Denied

Um diese abzufangen legt Ihr in config/packages/security.yaml unter security folgenden Parameter mit Pfad /login an

access_denied_url: /login

Damit werdet Ihr bei jedem Zugriff auf gesperrte Ressourcen auf /login geleitet.

Logout

Um eure Benutzer nun auszuloggen benötigt Ihr erstmal den richtigen Pfad in eurem Konfigurationparameter logout

Also auf zur security.yaml und eine path für logout hinzufügen. Diesen könnt Ihr dann in eurer routes.yaml benutzen, like so:

app_logout:
  path: /logout

Damit könnt Ihr euch über /logout ausloggen oder eine URL generieren

{% if is_granted('IS_AUTHENTICATED_FULLY') %}
<li class="nav-item">
    <a href="{{ logout_path(key = null) }}" class="nav-link">{{ 'menu.logout.name'|trans }}</a>
</li>
{% endif %}

Das ganze soll natürlich nur angezeigt werden wenn der User (egal ob Admin oder Helfer) eingeloggt ist, daher die IF-CLAUSE drum herum.

That’s it – damit steht das Grundgerüst der Benutzerverwaltung inkl. Authentifikation. Gefühlt geht das in Cakephp etwas leichter – aber das ist subjektiv, da ich schon so lange mit Cakephp gearbeitet habe.

Alles in allem hat mich das mit dem schreiben des Artikels ca. 8 Stunden gekostet. Wäre ohne Dokumentation einiger Schritte sicher schneller gegangen, allerdings kann ich so selbst ab und zu nochmal auf meinen Artikel zurückgreifen und mir so mühseliges suchen im WWW ersparen.

Anregungen zum Thema oder Fehler gefunden? Schreibt gerne in die Kommentare!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert