1. Magazin
  2. /
  3. Programmierung
  4. /
  5. Laravel 5.7 Multi-Authentication – Unterschiedliche Benutzertypen in 8 Schritten

Laravel 5.7 Multi-Authentication – Unterschiedliche Benutzertypen in 8 Schritten

Authentication

Laravel 5.7 Multi-Authentication – Unterschiedliche Benutzertypen in 8 Schritten

Laravel ist ein sehr mächtiges PHP Framework das seinen Fokus auf einfachen, simplen Code legt. Es bringt alle Werkzeuge mit, die ein Webentwickler benötigt, um eine zeitgemäße Online-Plattform zu entwickeln. Ein Beispiel ist die Integration von Benutzern. Mit einem Befehl in der Konsole lässt sich Laravels vordefinierte Implementierung der Benutzer in die Plattform integrieren.

Problematischer ist es hingegen wenn man mehrere Benutzertypen benötigt. Unter Benutzertypen verstehe ich die Differenzierung von Benutzern, die nicht das selbe User Model verwenden, also unterschiedliche Eigenschaften innehaben. Ein einfaches Beispiel wäre die Unterscheidung der Benutzertypen in den Plattform-Administrator, der die Plattformeinstellungen verwalten kann und Zugriff auf Logs und Statistiken der Plattform hat, sowie den normalen Benutzer, der Zugriff auf den Service der jeweiligen Plattform hat. Dieser kann bspw. Einkäufe tätigen, ein Abonnement abschließen, oder sich an Deiner Community beteiligen.

Du benötigst keine unterschiedlichen Benutzertypen, wenn sich Deine geplanten Benutzer das selbe User Model teilen und auf der Plattform nur mit unterschiedlichen Rechten agieren. Ein einfaches Beispiel dafür sind die typischen CMS Benutzerrollen wie Mitarbeiter, Autor und Redakteur, bei denen sich die Rechte der Benutzer zunehmend staffeln. Strebst Du ein solches Modell an, solltest Du Dirüberlegen, die Standardimplementierung der Benutzer um ein Rollen- und Rechtemodell zu erweitern.

In diesem Blogartikel werde ich Dir zeigen, wie Du in acht Schritten Deine eigenen Benutzertypen in Deine Laravel Anwendung integrieren kannst. Du wirst zunächst die Standardimplementierung von Laravel erweitern, danach wirst Du die benötigten Views und Routen bereitstellen. Ein weiterer wichtiger Punkt ist die Erweiterung der Authentication Middleware und die Einrichtung der Redirects. Zum Schluss stellst Du Deinerm neuen Benutzertypen noch die Möglichkeit zur Verfügung sich abzumelden und sein Passwort zurückzusetzen.

Also Grundlage setze ich voraus, dass Du kein Einsteiger in Laravel bist. Du solltest die Grundbausteine von Laravel kennen und wissen wie Du Sie anwendest. Artisan, Controller, Views, Middlewares und Traits sollten für Dich keine Fremdwörter sein. Wir steigen im nächsten Abschnitt direkt mit der Implementierung ein, ein Projekt sollte also bereits bestehen.

Die Integration der normalen Laravel Authentication

Zur Vorbereitung der unterschiedlichen Benutzertypen integrierst Du zunächst die Standard Authentication von Laravel. Auf dieser baust Du Schritt für Schritt Deinen neuen Benutzertypen auf. Du verwendest Sie als Vorlage und sparst Dir damit viel Zeit. Du passt die integrierte Standard Authentication aber nicht an, sondern Du adaptierst sie. Um später zwei unterschiedliche Typen zu verwenden musst Du Dich lediglich an der Standard Implementierung entlanghangeln und baust Dir neues User Model.

Richte nun über Artisan die Authentication ein:

$ php artisan make:auth

Artisan generiert für Dich nun einige Dinge, wie unter anderem Views zur Anmeldung, Registrierung und für Dein Dashboard, sowie Routen zu den neuen Seiten und einen Controller (HomeController.php) für Dein Dashboard. Im app Order findest Du das User Model, an dieser Stelle erstellst Du gleich das neue User Model. All das machst Du im Zuge einer Datenbank Migration.

Unter Deinen Migrationen /database/migrations/ befinden sich bereits zwei vorgefertigte Migrationen von Laravel. Diese wurden nicht von Artisan hinzugefügt. Die Migrationen erzeugen Deine „users“ und Deine „forgot my password“ Tabelle. In der Tabelle „users“ werden unsere normalen Benutzer gespeichert, diese adaptierst Du im nächsten Schritt. Die Tabelle für „forgot my password“ musst Du nicht für Deinen neuen Benutzertyp dublizieren, denn alle Benutzertypen können diese gemeinsam benutzen.

In den nachfolgenden Beispielen verwende ich den Benutzertyp für den B2B Bereich „BusinessUser“ als representativen neuen Benutzertypen.

Erstelle nun mit Artisan im Terminal eine neue Migration um Deine Benutzer des neuen Benutzertypes speichern zu können:

$ php artisan make:migration create_business_users_table --create=business_users

Die gerade erstellte Migration wird mit einer Datierung versehen und unter /database/migrations/ gespeichert. Durch die flag „–create=business_users“ wird die Migration bereits so angepasst, dass die Tabelle mit dem Namen „business_users“ angelegt wird.

Eine alternative Herangehensweise wäre es, das Model über Artisan anzulegen und dabei die passende Migration zu erstellen:

$ php artisan make:model BusinessUser -m

Jetzt musst Du die Migrationsdatei um die Eigenschaften Deines neuen Benutzertypes erweitern. Suche die Migrationsdatei im Verzeichnis /database/migrations/ und öffne sie. Passe die benötigten Eigenschaften so an, dass die Tabelle über alle benötigten Spalten verfügt. Nachfolgend findest Du die angepasste Migration des BusinessUser Models:

<?php

use IlluminateSupportFacadesSchema; 
use IlluminateDatabaseSchemaBlueprint; 
use IlluminateDatabaseMigrationsMigration; 

class CreateBusinessUsersTable extends Migration 
{ 
    /** 
     * Run the migrations. 
     * 
     * @return void 
     */ 
    public function up() 
    { 
        Schema::create('business_users', function (Blueprint $table) { 
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('company');
            $table->string('vat');
            $table->string('street_name');
            $table->string('street_number');
            $table->string('city');
            $table->string('zip');
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('business_users');
    }
}

Solltest Du Fragen zum Erstellen von Migrationen haben, bietet Dir die Laravel Dokumentation ausreichend Informationen und Beispiele an: Laravel Dokumentation – Migrationen

In dieser Migration haben wir einige Spalten für die Eigenschaften unseres neuen Benutzertypes hinzugefügt, die dafür sorgen, das sich dieser von unserem User Benutzertyp unterscheidet. Beachte, dass die Spalte „email_verified_at“ Bestandteil des aus Laravel 5.7 stammenden, neuen Features zur E-Mail Verifizierung ist.

Durch das Hinzufügen von $table->rememberToken(); wird für das Model die Funktionalität „Angemeldet bleiben“ aktiviert und wir können dieses in der Login Maske für unseren neuen Benutzertypen nutzen. Im nächsten Schritt können wir die Migration bereits durchführen.

Hinweis: Da ich für diesen Artikel Vorwissen in der Entwicklung von Laravel Applikationen voraussetze gehe ich nicht auf die .env Konfiguration ein. Denke bitte daran, Deine Environment Datei zu konfigurieren und eine Datenbank einzurichten bevor Du die Migrationen ausführst.

$ php artisan migrate

Die Tabellen für die Authentication der normalen Benutzer und der B2B User wurden nun integriert. Im nächsten Schritt schauen wir uns die zugehörigen User Models an.

Aufsetzen der User Models

Das User Model für den normalen Benutzer existiert bereits im Ordner app. Das User Model funktioniert auch bereits und kann direkt verwendet werden. Du kannst das existierende User Model User.php nun einfach dublizieren und umbenennen. In diesem Beispiel heißt das neue Model BusinessUser.php. Lass uns nun einen Blick in das neue Model werfen:

<?php

namespace App;

use IlluminateNotificationsNotifiable;
use IlluminateContractsAuthMustVerifyEmail;
use IlluminateFoundationAuthUser as Authenticatable;

class BusinessUser extends Authenticatable implements MustVerifyEmail
{
	use Notifiable;

	protected $guard = 'business_user';

	/**
	 * The attributes that are mass assignable.
	 *
	 * @var array
	 */
	protected $fillable = [
		'name',
		'email',
		'password',
		'company',
		'vat',
		'street_name',
		'street_number',
		'city',
		'zip'
	];

	/**
	 * The attributes that should be hidden for arrays.
	 *
	 * @var array
	 */
	protected $hidden = [
		'password', 'remember_token',
	];
}

Durch die Duplizierung des User Model hast Du alle relevanten Bestandteile für Dein eigenes User Model bereits integriert. Du solltest aber noch ein paar Anpassungen vornehmen.

Zeile 9: Passe den Klassennamen an Dein User Model an. Im Beispiel wurde User in „BusinessUser“ geändert.

Zeile 9: Implementiere in der Zeile 8 noch das Interface „MustVerifyEmail“. Dadurch muss der Benutzer nach der Registrierung seine E-Mail Adresse bestätigen. Der Benutzer erhält automatisch die Notification „NotificationsVerifyEmail“, in der sich ein Link zur E-Mail Bestätigung befindet. Das Interface kannst Du übrigens auch im User Model User.php hinzufügen, damit auch normale Benutzer ihre E-Mail Adresse bestätigen müssen.

Zeile 13: Um später den Zugriff auf Controller nur für Benutzer des neuen Benutzertypes zu beschränken hinterlegst Du in der protected Eigenschaft „guard“ den Namen Deines User Models. Im nächsten Schritt stellen wir die passende guard in der auth.php Konfiguration bereit.

Zeile 20: Integriere in das fillable Array all Deine Benutzereigenschaften, die per „create“ befüllt werden können.

Aufsetzen des Guards

In Laravel ist es möglich mehrere konfigurierte Benutzertypen zu identifizieren. Die Zugriffsbeschränkung wird mit sogenannten „Guards“ vorgenommen. In Laravel können so viele Guards konfiguriert werden, wie Du sie benötigst, es gibt keine Einschränkung.

Die Standard Guards und neue Guards werden in der Authentication Konfiguration unter config/auth.php verwaltet und konfiguriert. Im folgenden Beispiel siehst Du die Standard auth.php ohne die Kommentare:

<?php

return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => AppUser::class,
        ],
    ],
…

Im ersten Teil des Arrays siehst Du die Standardeinstellungen. Als Standardwert für guard ist „web“ festgelegt. Sollte bei einer Middleware keine Guard festgelegt sein, wird automatisch die „web“ Guard verwendet.

Im nächsten Array Knoten „guards“ sind die vorhandenen Guards „web“ und „api“ hinterlegt. Beide teilen sich den Provider „users“. Unterscheiden tuen sie sich lediglich über den hinterlegten Driver. Bei einem Web Zugriff soll ein Session/ Cookie basierter Treiber verwendet werden und bei einem API-Request ein Token basierter.

Direkt unterhalb der Guards findest Du die Provider. Als einziger Provider sollte dort momentan „users“ hinterlegt sein. „users“ verwendet als Driver wiederum Eloquent und das zugeordnete User Model ist AppUser. An dieser Stelle kann auch database als Driver hinterlegt werden und anstelle des User Models trägst Du in diesem Fall den Tabellennamen ein.

Im nächsten Schritt legst Du Deinen eigenen Provider und Deine eigenen Guards an. Für dieses Tutorial beschränke ich mich im Detail darauf, einen Guard zu integrieren, der Session basiert arbeitet. Füge nun die Knoten an die jeweiligen Arrays an, Deine auth config sollte so ähnlich aussehen, wie in folgendem Beispiel:

<?php

return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
        'business_user' => [
            'driver' => 'session',
            'provider' => 'business_users',
        ],
        'business_user_api' => [
            'driver' => 'token',
            'provider' => 'business_users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => AppUser::class,
        ],
        'business_users' => [
            'driver' => 'eloquent',
            'model' => AppBusinessUser::class,
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        'business_users' => [
	        'provider' => 'business_users',
	        'table' => 'password_resets',
	        'expire' => 10,
        ],
    ],

];

Stelle sicher, dass beim Model das richtige Model hinterlegt ist und dass Du die ::class Syntax verwendest.

Nun steht Dir der relevante Guard für Dein User Model zur Verfügung. Dieser steht jetzt in Verbindung mit Deinem User Model und in dem sollte jetzt auch die protected Eigenschaft mir dem Name der Guard übereinstimmen.

Testen der Zugriffsbeschränkung

Zum Testen der Zugriffsbeschränkung dublizierst Du in diesem Schritt die „home“ View. Dadurch baust Du ein Dashboard/ eine Startseite für Deinen neuen Benutzertyp. Auf diese Seite haben lediglich angemeldete Business User Zugriff. Gäste werden auf den Business User Login weitergeleitet und normale Benutzer ebenfalls. Da normale Benutzer aber bereits angemeldet sind, werden diese vom Login auf ihre eigene Startseite weitergeleitet.

Erstelle nun einen „Dashboard“ Controller für Deinen neuen Benutzertyp:

$ php artisan make:controller BusinessUserHomeController

In dem erzeugten Controller kannst Du die Funktion „index“ vom HomeController adaptieren:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use IlluminateHttpResponse;

class BusinessUserHomeController extends Controller
{
	/**
	 * Create a new controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		$this->middleware('auth:business_user');
	}

	/**
	 * Show the application dashboard.
	 *
	 * @return Response
	 */
	public function index()
	{
		return view('businessuser.home');
	}
}

Wie Du im Konstruktor erkennen kannst, erweitere ich im Beispiel die Middleware „auth“ um einen Parameter, was an dem Doppelpunkt zu erkennen ist. Nach dem Doppelpunkt folgt die Guard, die wir in den vorherigen Schritten definiert haben :business_user. Mehr zur Verwendung von Parametern in der Middleware findest Du unter: Laravel Dokumentation – Middleware. In der Funktion „index“ hinterlegst Du noch Deine View, in meinem Fall ist das „businessuser.home“.

Nun kannst Du die „home“ View kopieren und änderst zu Testzwecken die Dashboad-Bezeichnungen, damit ersichtlich ist, welche View geladen wurde:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Dashboard für Geschäftskunden</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    Sie befinden sich im Dashboard für Geschäftskunden.
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Zuletzt hinterlegst Du noch die Route für Deine neue Dashboard View in der Datei routes/web.php:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
Route::get('/business-user/home', 'BusinessUserHomeController@index')->name('business_user.home');

Greifst Du nun als angemeldeter normaler Benutzer auf die Seite „/business-user/home“ zu, wirst Du auf Dein Dashboard „/home“ redirected.

Registrierung unterschiedlicher Benutzertypen

Kommen wir nun zur Registrierung. Über die Standardregistrierung, die mit „make:auth“ angelegt wurde, können keine eigenen Benutzertypen registriert werden. Ich würde kein Auswahlfeld für den Benutzertypen hinzufügen, da normale Benutzer ansonsten verwirrt seien könnten. Da der neue Benutzertyp sowieso andere Eigenschaften haben sollte als der Standardbenutzer, teilen sich diese nicht die Felder bei der Registrierung. Wenn es sich bei Deinem Benutzertypen um einen Administrator handelt, benötigst Du keine weitere Registrierung. Diesen kannst Du dann über einen Seed anlegen. Wie das funktioniert zeige ich Dir im nächsten Kapitel. Nun erst mal zur Registrierung. Erzeuge einen neuen Registrierungs Controller im Unterordner „Auth“:

$ php artisan make:controller Auth/RegisterBusinessUserController

Nun übernimmst Du schrittweise alle wichtigen Komponenten des RegisterController:

<?php

namespace AppHttpControllersAuth;

use AppBusinessUser;
use AppHttpControllersController;
use IlluminateFoundationAuthUser;
use IlluminateHttpResponse;
use IlluminateSupportFacadesHash;
use IlluminateSupportFacadesValidator;
use IlluminateFoundationAuthRegistersUsers;

class RegisterBusinessUserController extends Controller
{
	use RegistersUsers;

	/**
	 * Where to redirect users after registration.
	 *
	 * @var string
	 */
	protected $redirectTo = '/business-user/home';

	/**
	 * Create a new controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		$this->middleware('guest');
	}

	/**
	 * Show the application registration form.
	 *
	 * @return Response
	 */
	protected function showRegistrationForm()
	{
		return view('businessuser.auth.register');
	}

	/**
	 * Get a validator for an incoming registration request.
	 *
	 * @param  array  $data
	 * @return IlluminateContractsValidationValidator
	 */
	protected function validator(array $data)
	{
		return Validator::make($data, [
			'name' => 'required|string|max:255',
			'email' => 'required|string|email|max:255|unique:users',
			'password' => 'required|string|min:6|confirmed',
			'company' => 'required|string|max:255',
			'vat' => 'required|string|max:255',
			'street_name' => 'required|string|max:255',
			'street_number' => 'required|string|max:255',
			'city' => 'required|string|max:255',
			'zip' => 'required|string|max:255',
		]);
	}

	/**
	 * Create a new user instance after a valid registration.
	 *
	 * @param  array  $data
	 * @return User
	 */
	protected function create(array $data)
	{
		return BusinessUser::create([
			'name' => $data['name'],
			'email' => $data['email'],
			'password' => Hash::make($data['password']),
			'company' => $data['company'],
			'vat' => $data['vat'],
			'street_name' => $data['street_name'],
			'street_number' => $data['street_number'],
			'city' => $data['city'],
			'zip' => $data['zip']
		]);
	}
}

Zunächst integrierst Du den Trait RegistersUsers. Über die protected Eigenschaft redirectTo kannst Du das Redirect Ziel nach einer erfolgreichen Registrierung festlegen. Im Konstruktor legst Du über die Middleware „Guest“ fest, dass angemeldete Benutzer die Seite nicht besuchen können. Diese werden auf ihr Dashboard weitergeleitet.

Zum Schluss überschreibst Du noch die Funktionen „validator“ und „create“. Du erweiterst die Validierung um die Felder Deines Registrierungsformulars und in der Erstellungsfunktion initialisierst Du über die statische create Funktion Dein User Model und gibst es zurück. Dadurch, dass Dein User Model von der Basisklasse Authenticatable abgeleitet ist, werden alle weiteren Schritte durch die Funktionen im Trait erledigt.

Um den Prozess der Registrierung Deines eigenen Benutzertypen zu vervollständigen fehlt nun nur noch eine View. Im Controller überschreibst Du die Trait Funktion „showRegistrationForm“ und gibst dort die View Deiner Wahl zurück. Du kopierst nun die register View der normalen Registrierung und fügst die von Dir benötigten Felder hinzu. Hier findest Du die modifizierte View, mit den Eigenschaften aus meinem neuen Benutzertypen:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Geschäftskundenregistrierung</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('business_user.register.post') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">Geschäftsführung</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>

                                @if ($errors->has('name'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="company" class="col-md-4 col-form-label text-md-right">Firma</label>

                            <div class="col-md-6">
                                <input id="company" type="text" class="form-control{{ $errors->has('company') ? ' is-invalid' : '' }}" name="company" value="{{ old('company') }}" required>

                                @if ($errors->has('company'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('company') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="vat" class="col-md-4 col-form-label text-md-right">USt-ID</label>

                            <div class="col-md-6">
                                <input id="vat" type="text" class="form-control{{ $errors->has('vat') ? ' is-invalid' : '' }}" name="vat" value="{{ old('vat') }}" required>

                                @if ($errors->has('vat'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('vat') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="street_name" class="col-md-4 col-form-label text-md-right">Straße</label>

                            <div class="col-md-6">
                                <input id="street_name" type="text" class="form-control{{ $errors->has('street_name') ? ' is-invalid' : '' }}" name="street_name" value="{{ old('street_name') }}" required>

                                @if ($errors->has('street_name'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('street_name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="street_number" class="col-md-4 col-form-label text-md-right">Hausnummer</label>

                            <div class="col-md-6">
                                <input id="street_number" type="text" class="form-control{{ $errors->has('street_number') ? ' is-invalid' : '' }}" name="street_number" value="{{ old('street_number') }}" required>

                                @if ($errors->has('street_number'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('street_number') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="city" class="col-md-4 col-form-label text-md-right">Ort</label>

                            <div class="col-md-6">
                                <input id="city" type="text" class="form-control{{ $errors->has('city') ? ' is-invalid' : '' }}" name="city" value="{{ old('city') }}" required>

                                @if ($errors->has('city'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('city') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="zip" class="col-md-4 col-form-label text-md-right">PLZ</label>

                            <div class="col-md-6">
                                <input id="zip" type="text" class="form-control{{ $errors->has('zip') ? ' is-invalid' : '' }}" name="zip" value="{{ old('zip') }}" required>

                                @if ($errors->has('zip'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('zip') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">Passwort</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">Passwort wiederholen</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">Jetzt registrieren</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Als nächstes platzierst Du in Deiner „welcome“ View einen Link auf die neue Registrierung:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <style>
            html, body {
                background-color: #fff;
                color: #636b6f;
                font-family: 'Nunito', sans-serif;
                font-weight: 200;
                height: 100vh;
                margin: 0;
            }

            .full-height {
                height: 100vh;
            }

            .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }

            .position-ref {
                position: relative;
            }

            .top-right {
                position: absolute;
                right: 10px;
                top: 18px;
            }

            .content {
                text-align: center;
            }

            .title {
                font-size: 84px;
            }

            .links > a {
                color: #636b6f;
                padding: 0 25px;
                font-size: 12px;
                font-weight: 600;
                letter-spacing: .1rem;
                text-decoration: none;
                text-transform: uppercase;
            }

            .m-b-md {
                margin-bottom: 30px;
            }
        </style>
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            @if (Route::has('login'))
                <div class="top-right links">
                    @auth
                        <a href="{{ url('/home') }}">Home</a>
                    @else
                        <a href="{{ route('login') }}">Login</a>
                        <a href="{{ route('register') }}">Registrierung</a>
                        <a href="{{ route('business_user.register.get') }}">Geschäftskundenregistrierung</a>
                    @endauth
                </div>
            @endif

            <div class="content">
                <div class="title m-b-md">
                    Laravel
                </div>

                <div class="links">
                    <a href="https://laravel.com/docs">Documentation</a>
                    <a href="https://laracasts.com">Laracasts</a>
                    <a href="https://laravel-news.com">News</a>
                    <a href="https://nova.laravel.com">Nova</a>
                    <a href="https://forge.laravel.com">Forge</a>
                    <a href="https://github.com/laravel/laravel">GitHub</a>
                </div>
            </div>
        </div>
    </body>
</html>

Damit die integrierten Routen funktionieren, müssen diese noch in Deinen Web-Routen hinterlegt werden:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home/', 'HomeController@index')
     ->name('home');

Route::prefix('business-user')->group(function () {
	Route::get('/home/', 'BusinessUserHomeController@index')
	     ->name('business_user.home');

	Route::get('/register/', 'AuthRegisterBusinessUserController@showRegistrationForm')
	     ->name('business_user.register.get');
	Route::post('/register/', 'AuthRegisterBusinessUserController@register')
	     ->name('business_user.register.post');
});

Zur besseren Gliederung der Routen wurden diese im einem Routen Prefix gruppiert. Du kannst Dir nun mit Hilfe von Artisan Deine Routing-Tabelle anzeigen lassen:

$ php artisan route:list

Oder die Kurzform:

$ php artisan r:l

Eigene E-Mail-Verifizierung – Seit Laravel 5.7

Diesen Schritt kannst Du nur integrieren, wenn Du mit einem Laravel 5.7 Projekt arbeitest. Vorher gab es die eigens von Laravel bereitgestellte E-Mail-Verifizierung noch nicht.

Im nächsten Schritt integrierst Du das E-Mail-Verifizierungsverfahren. Laravel hat in der Version 5.7 ein eigenes E-Mail Verifizierungsverfahren erhalten. Dieses ist ebenfalls abgestimmt auf den Standard Laravel Benutzer und Du integrierst nun Dein eigenes Verifizierungsverfahren, basiert auf dem von Laravel. Da Du zuletzt Routen in der web.php ergänzst hast setzen wir hier auch wieder an.
In der Zeile 18 muss beim Aufruf von Auth::routes(); noch ein Array mit Argumenten übergeben werden. Als Parameter setzt Du wie im folgenden Beispiel verify = true.

Im Prefixknoten „business-user“ integrierst Du nun drei neue Verifizierungsrouten:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes(['verify' => true]);

Route::get('/home/', 'HomeController@index')
     ->name('home');

Route::prefix('business-user')->group(function () {
	Route::get('/home/', 'BusinessUserHomeController@index')
	     ->name('business_user.home');

	Route::get('/register/', 'AuthRegisterBusinessUserController@showRegistrationForm')
	     ->name('business_user.register.get');
	Route::post('/register/', 'AuthRegisterBusinessUserController@register')
	     ->name('business_user.register.post');

	Route::get('/email/resend/', 'BusinessUserVerificationController@resend')
	     ->name('business_user.verification.resend');
	Route::get('/email/verify/', 'BusinessUserVerificationController@show')
	     ->name('business_user.verification.notice');
	Route::get('/email/verify/{id}/', 'BusinessUserVerificationController@verify')
	     ->name('business_user.verification.verify');
});

Lege nun den Verifizierungs-Controller über Artisan an:

$ php artisan make:controller BusinessUserVerificationController

In dem Controller impelementierst Du alle Inhalte des AuthVerificationController, dieser dient nun als Grundlage.

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use IlluminateHttpResponse;
use IlluminateFoundationAuthVerifiesEmails;

class BusinessUserVerificationController extends Controller
{
	use VerifiesEmails;

	/**
	 * Where to redirect users after verification.
	 *
	 * @var string
	 */
	protected $redirectTo = '/business-user/home';

	/**
	 * Create a new controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		$this->middleware('auth:business_user');
		$this->middleware('signed')->only('verify');
		$this->middleware('throttle:6,1')->only('verify', 'resend');
	}

	/**
	 * Show the email verification notice.
	 *
	 * @param  Request  $request
	 * @return Response
	 */
	public function show(Request $request)
	{
		return $request->user()->hasVerifiedEmail()
			? redirect($this->redirectPath())
			: view('businessuser.verify');
	}
}

Am Controller musst Du nun noch einige Anpassungen durchführen. Wie im Beispiel zu sehen ist, wurde die protected Eigenschaft redirectTo an die Dashboard URL des neuen Benutzertypen angepasst und die „auth“ Middleware wurde um den Parameter „business_user“ erweitert. Weiterhin wurde die Funktion „show“ aus dem VerifiesEmails Trait überschrieben, damit der Benutzer korrekt weitergeleitet wird.

Als nächstes musst Du das E-Mail-Template/ die Laravel Notification für die Verifizierung anpassen. Der Link in der E-Mail zeigt auf die falsche Verifizierungsseite. Zunächst implementierst Du in Deinem neuen User Model, bei mir ist das AppBusinessUser.php, die Funktion „sendEmailVerificationNotification“, die vom Interface „MustVerifyEmail“ vorgegeben wird. Du musst nicht alle Funktionen des Interfaces integrieren. Diese kommen aus dem Trait IlluminateAuthMustVerifyEmail, der im Authenticatable integriert wird.

In der Funktion integrierst Du nun Deine eigene Notification, die Du im Anschluss erstellst:

<?php

namespace App;

use IlluminateNotificationsNotifiable;
use IlluminateContractsAuthMustVerifyEmail;
use IlluminateFoundationAuthUser as Authenticatable;

class BusinessUser extends Authenticatable implements MustVerifyEmail
{
	use Notifiable;

	/**
	 * The attributes that are mass assignable.
	 *
	 * @var array
	 */
	protected $fillable = [
		'name',
		'email',
		'password',
		'company',
		'vat',
		'street_name',
		'street_number',
		'city',
		'zip'
	];

	/**
	 * The attributes that should be hidden for arrays.
	 *
	 * @var array
	 */
	protected $hidden = [
		'password', 'remember_token',
	];

	/**
	 * Versendet eine E-Mail-Verifizierungs Benachrichtigung an den Benutzer
	 */
	public function sendEmailVerificationNotification()
	{
		$this->notify(new NotificationsBusinessUserVerifyEmail);
	}
}

Erstelle jetzt mit Artisan eine Notification:

$ php artisan make:notification BusinessUserVerifyEmail

Laravel hat nun im Verzeichnis app/Notifications die neue Notification BusinessUserVerifyEmail angelegt. Nun müssen wir den Inhalt der alten Notification ermitteln und übernehmen. Das einzige was wir anpassen müssen, ist die temporäre URL zur Aktivierung. Die ursprüngliche Notification ist im Trait MustVerifyEmail hinterlegt und ist im Namespace IlluminateAuthNotifications enthalten:

<?php

namespace AppNotifications;

use IlluminateBusQueueable;
use IlluminateNotificationsNotification;
use IlluminateContractsQueueShouldQueue;
use IlluminateNotificationsMessagesMailMessage;
use IlluminateSupportCarbon;
use IlluminateSupportFacadesLang;
use IlluminateSupportFacadesURL;

class BusinessUserVerifyEmail extends Notification implements ShouldQueue
{
	use Queueable;

	/**
	 * The callback that should be used to build the mail message.
	 *
	 * @var Closure|null
	 */
	public static $toMailCallback;

	/**
	 * Get the notification's delivery channels.
	 *
	 * @param  mixed  $notifiable
	 * @return array
	 */
	public function via($notifiable)
	{
		return ['mail'];
	}

	/**
	 * Get the mail representation of the notification.
	 *
	 * @param  mixed  $notifiable
	 * @return IlluminateNotificationsMessagesMailMessage
	 */
	public function toMail($notifiable)
	{
		if (static::$toMailCallback) {
			return call_user_func(static::$toMailCallback, $notifiable);
		}
	
		return (new MailMessage)
			->subject(Lang::getFromJson('Verify Email Address'))
			->line(Lang::getFromJson('Please click the button below to verify your email address.'))
			->action(
				Lang::getFromJson('Verify Email Address'),
				$this->verificationUrl($notifiable)
			)
			->line(Lang::getFromJson('If you did not create an account, no further action is required.'));
	}

	/**
	 * Get the verification URL for the given notifiable.
	 *
	 * @param  mixed  $notifiable
	 * @return string
	 */
	protected function verificationUrl($notifiable)
	{
		return URL::temporarySignedRoute(
			'business_user.verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()]
		);
	}

	/**
	 * Set a callback that should be used when building the notification mail message.
	 *
	 * @param  Closure  $callback
	 * @return void
	 */
	public static function toMailUsing($callback)
	{
		static::$toMailCallback = $callback;
	}
}

In diesem Beispiel habe ich noch das Interface ShouldQueue implementiert und in den Zeilen 65-67 die temporär signierte Route auf die Verifizierungsseite des BusinessUser‘s geändert.

Anlegen von Benutzern über Database Seeding

Dieser Schritt ist zur Integration nicht notwendig. Es ist Dir selbst überlassen, ob Du das Database Seeding für Deinen eigenen Benutzertypen benötigst.

Wenn Dein neuer Benutzertyp bspw. ein Administrator ist und Du nicht möchtest, dass sich dieser registrieren kann, so kannst Du das Database Seeding von Laravel nutzen. Detaillierte Informationen zum Seeding findest Du unter: Laravel Dokumentation – Seeding.

Seeding ermöglicht Dir, über einen Befehl Deine Datenbank mit Daten zu befüllen. So kannst Du einen Prozess entwickeln, um einen default Administrator anzulegen, sollte noch keiner im System hinterlegt sein. Um Dir einen Administratoren Seed anzulegen, führe folgenden Artisan Befehl im Terminal aus:

$ php artisan make:seeder AdminTableSeeder

Unter database/seeds wurde nun Dein Seeder AdminTableSeeder angelegt. Um einen default Administrator zu hinterlegen, kannst Du folgendes Beispiel-Script als Grundlage verwenden:

<?php

use AppAdministrator;
use IlluminateDatabaseSeeder;
use IlluminateSupportFacadesHash;
use IlluminateDatabaseEloquentCollection;

class AdminTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        /** @var Collection $administrators */
        $administrators  = Administrator::all();

        if($administrators->isEmpty()) {
            Administrator::create([
            	'name' => 'Administrator',
	            'email' => 'info@email.de',
	            'password' => Hash::make('insecure')
            ])
        }
    }
}

Damit Dein Seeder verwendet werden kann, musst Du ihn im DatabaseSeeder hinzufügen:

<?php

use IlluminateDatabaseSeeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(AdminTableSeeder::class);
    }
}

Um den Seeder auszuführen wechselst Du in Dein Terminal und generierst Composers autoload neu:

$ composer dump-autoload

Danach führst Du das Seeding über Artisan aus:

$ php artisan db:seed

Du hast auch die Möglichkeit das Seeding während der Migrations durchzuführen:

$ php artisan migrate:refresh --seed

Integration des Logins

In diesem Abschnitt integrierst Du einen neuen Login Bereich für den neuen Benutzertyp. Der aktuell verfügbare Login-Bereich ist starr auf das User Model ausgerichtet und es logged auch nur diesen ein.

Um den Login für den Business User bereitzustellen musst Du nun einige Komponenten dses regulären Logins adaptieren und ein wenig anpassen. Du benötigst einen neuen Controller, einen eigenen AuthenticatsUsers Trait und eine eigene Login View. Des weitern musst Du noch die neuen Routen hinterlegen. All das gehen wir nun Schritt für Schritt durch.

Beginnen wir mit dem Trait. Lege den Ordner „Traits“ bei Deinen Auth Controlleren an (app/Http/Controllers/Auth/Traits). In diesem erstellst Du den Trait AuthenticatesBusinessUsers, der zunächst eine exakte Kopie des Traits AuthenticatesUsers aus dem Namespace „IlluminateFoundationAuth“ ist.

Im Trait passt Du zunächst den Namespace an und fügst die usings für „IlluminateFoundationAuthRedirectsUsers“ und „IlluminateFoundationAuthThrottlesLogins“ hinzu, damit die eingebundenen Traits weiterhin funktionieren.

Zum Schluss passt Du noch die showLoginForm und die verwendete Guard an. In folgendem Beispiel findest Du alle Anpassungen hervorgehoben:

<?php

namespace AppHttpControllersAuthTraits;

use IlluminateFoundationAuthRedirectsUsers;
use IlluminateFoundationAuthThrottlesLogins;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateValidationValidationException;

trait AuthenticatesBusinessUsers
{
	use RedirectsUsers, ThrottlesLogins;

	/**
	 * Show the application's login form.
	 *
	 * @return IlluminateHttpResponse
	 */
	public function showLoginForm()
	{
		return view('businessuser.auth.login');
	}

	/**
	 * Handle a login request to the application.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return IlluminateHttpRedirectResponse|IlluminateHttpResponse|IlluminateHttpJsonResponse
	 *
	 * @throws IlluminateValidationValidationException
	 */
	public function login(Request $request)
	{
		$this->validateLogin($request);

		// If the class is using the ThrottlesLogins trait, we can automatically throttle
		// the login attempts for this application. We'll key this by the username and
		// the IP address of the client making these requests into this application.
		if ($this->hasTooManyLoginAttempts($request)) {
			$this->fireLockoutEvent($request);

			return $this->sendLockoutResponse($request);
		}

		if ($this->attemptLogin($request)) {
			return $this->sendLoginResponse($request);
		}

		// If the login attempt was unsuccessful we will increment the number of attempts
		// to login and redirect the user back to the login form. Of course, when this
		// user surpasses their maximum number of attempts they will get locked out.
		$this->incrementLoginAttempts($request);

		return $this->sendFailedLoginResponse($request);
	}

	/**
	 * Validate the user login request.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return void
	 */
	protected function validateLogin(Request $request)
	{
		$this->validate($request, [
			$this->username() => 'required|string',
			'password' => 'required|string',
		]);
	}

	/**
	 * Attempt to log the user into the application.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return bool
	 */
	protected function attemptLogin(Request $request)
	{
		return $this->guard()->attempt(
			$this->credentials($request), $request->filled('remember')
		);
	}

	/**
	 * Get the needed authorization credentials from the request.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return array
	 */
	protected function credentials(Request $request)
	{
		return $request->only($this->username(), 'password');
	}

	/**
	 * Send the response after the user was authenticated.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return IlluminateHttpResponse
	 */
	protected function sendLoginResponse(Request $request)
	{
		$request->session()->regenerate();

		$this->clearLoginAttempts($request);

		return $this->authenticated($request, $this->guard()->user())
			?: redirect()->intended($this->redirectPath());
	}

	/**
	 * The user has been authenticated.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @param  mixed  $user
	 * @return mixed
	 */
	protected function authenticated(Request $request, $user)
	{
		//
	}

	/**
	 * Get the failed login response instance.
	 *
	 * @param  IlluminateHttpRequest $request
	 *
	 * @return void
	 */
	protected function sendFailedLoginResponse(Request $request)
	{
		throw ValidationException::withMessages([
			$this->username() => [trans('auth.failed')],
		]);
	}

	/**
	 * Get the login username to be used by the controller.
	 *
	 * @return string
	 */
	public function username()
	{
		return 'email';
	}

	/**
	 * Log the user out of the application.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return IlluminateHttpResponse
	 */
	public function logout(Request $request)
	{
		$this->guard()->logout();

		$request->session()->invalidate();

		return $this->loggedOut($request) ?: redirect('/');
	}

	/**
	 * The user has logged out of the application.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @return mixed
	 */
	protected function loggedOut(Request $request)
	{
		//
	}

	/**
	 * Get the guard to be used during authentication.
	 *
	 * @return IlluminateContractsAuthStatefulGuard
	 */
	protected function guard()
	{
		return Auth::guard('business_user');
	}
}

Da nun der angepasste Trait zur Verfügung steht, kannst Du den LoginController kopieren und anpassen. In diesem änderst Du den verwendeten Trait zu AuthenticatesBusinessUsers und den „redirect to“ Pfad zu Deinem neuen Home-Verzeichnis. Im Konstruktor änderst Du die Guard der „guest“ Middleware auf „guest:business_user“ damit angemeldete Benutzer dieser Guard nicht auf die Login Seite gelangen.

Hier findest Du den Code des angepassten Controllers. Die Angepassten Stellen sind wieder markiert:

<?php

namespace AppHttpControllersAuth;

use AppHttpControllersAuthTraitsAuthenticatesBusinessUsers;
use AppHttpControllersController;

class LoginBusinessUserController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Business User Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesBusinessUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = 'business-user/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest:business_user')->except('logout');
    }
}

Nun fehlen noch die Einträge der Routen und die Login-View. Diese integrierst Du im nächsten Schritt. Damit ist die Anmeldung auch bereits komplett.
Öffne die Routes Datei „web.php“ und füge die beiden Login Routen GET & POST in die Gruppierung ein. Die hinzugefügten Routen sind hervorgehoben:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes(['verify' => true]);

Route::get('/home/', 'HomeController@index')
     ->name('home');

Route::prefix('business-user')->group(function () {
	Route::get('/home/', 'BusinessUserHomeController@index')
	     ->name('business_user.home');

	Route::get('/register/', 'AuthRegisterBusinessUserController@showRegistrationForm')
	     ->name('business_user.register.get');
	Route::post('/register/', 'AuthRegisterBusinessUserController@register')
	     ->name('business_user.register.post');

	Route::get('/email/resend/', 'BusinessUserVerificationController@resend')
	     ->name('business_user.verification.resend');
	Route::get('/email/verify/', 'BusinessUserVerificationController@show')
	     ->name('business_user.verification.notice');
	Route::get('/email/verify/{id}/', 'BusinessUserVerificationController@verify')
	     ->name('business_user.verification.verify');

	Route::get('/login/', 'AuthLoginBusinessUserController@showLoginForm')
	     ->name('business_user.login');
	Route::post('/login/', 'AuthLoginBusinessUserController@login')
	     ->name('business_user.login.submit');
});

Kopiere nächstes die Login View im „auth“ Verzeichnis und füge sie in dem neuen Verzeichnis Deines neuen Benutzertypes ein. Passe dort die Überschrift, sowie die Post-Action an. Damit hast Du alle relevanten Anpassungen für Deinen eigenen Login Beriech vorgenommen:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Business User Login') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('business_user.login.submit') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <div class="col-md-6 offset-md-4">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>

                                    <label class="form-check-label" for="remember">
                                        {{ __('Remember Me') }}
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>

                                <a class="btn btn-link" href="{{ route('password.request') }}">
                                    {{ __('Forgot Your Password?') }}
                                </a>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Damit Du nun alle Redirects korrekt zum Laufen bringst, müssen wir noch einige Dateien von Laravel Anpassen und umschreiben. Beginne nun damit die Authenticate Mittleware unter „app/Http/Middleware“ folgendermaßen Anzupassen:

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateAuthMiddlewareAuthenticate as Middleware;

class Authenticate extends Middleware
{
	/**
	 * @var array
	 */
	protected $guards = [];

	/**
	 * Handle an incoming request.
	 *
	 * @param  IlluminateHttpRequest $request
	 * @param  Closure $next
	 * @param  string[] ...$guards
	 * @return mixed
	 *
	 * @throws IlluminateAuthAuthenticationException
	 */
	public function handle($request, Closure $next, ...$guards)
	{
		$this->guards = $guards;

		return parent::handle($request, $next, ...$guards);
	}

	/**
	 * Get the path the user should be redirected to when they are not authenticated.
	 *
	 * @param  IlluminateHttpRequest $request
	 * @return string
	 */
	protected function redirectTo($request)
	{
		if (!$request->expectsJson()) {
			if (array_first($this->guards) === 'business_user') {
				return route('business_user.login');
			}
			return route('login');
		}
	}
}

Danach öffnest Du die Klasse „RedirectIfAuthenticated“ im selben Verzeichnis und passt sie folgendermaßen an:

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateSupportFacadesAuth;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if ($guard == "business_user" && Auth::guard($guard)->check()) {
            return redirect()->route('business_user.home');
        }

        if (Auth::guard($guard)->check()) {
            return redirect('/home');
        }

        return $next($request);
    }
}

Da „MustVerifyEmail“ im User und im BusinessUser integriert wurden, müssen auch diese Redirects angepasst werden. Dafür legst Du Deine eigene Middleware an, die dann von der Ilumniate Middleware erbt. Dadurch können wir die „handle“ Funktion anpassen, ohne die Vendor-Klasse zu verändern.
Lege nun die Middleware im Terminal an:

$ php artisan make:middleware EnsureEmailIsVerified

Passe nun die erzeugte Middleware folgendermaßen an:

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateAuthMiddlewareEnsureEmailIsVerified as Middleware;
use IlluminateContractsAuthMustVerifyEmail;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateSupportFacadesRedirect;

class EnsureEmailIsVerified extends Middleware
{
	/**
	 * Handle an incoming request.
	 *
	 * @param  IlluminateHttpRequest $request
	 * @param  Closure $next
	 * @param  null $guard
	 *
	 * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
	 */
	public function handle($request, Closure $next, $guard = null)
	{
		/** @var array $config */
		$configGuards = array_keys(config('auth.guards'));

		/** @var string $guard */
		foreach($configGuards as $configGuard) {
			/** @var string $guardFunction */
			$guardFunction = sprintf('handleGuard_%1$s', $configGuard);

			if(method_exists($this, $guardFunction)) {
				/** @var null|IlluminateHttpRedirectResponse $result */
				$result = $this->{$guardFunction}($configGuard, $request);

				if($result instanceof IlluminateHttpRedirectResponse) {
					return $result;
				}
			}
		}

		return $next($request);
	}

	/**
	 * Behandelt Requests der "web" Guard.
	 *
	 * @param string $guard
	 * @param Request $request
	 *
	 * @return IlluminateHttpRedirectResponse|void
	 */
	private function handleGuard_web($guard, $request)
	{
		if (Auth::guard($guard)->check()) {
			if (! Auth::guard($guard)->user() ||
			    (Auth::guard($guard)->user() instanceof MustVerifyEmail &&
			     ! Auth::guard($guard)->user()->hasVerifiedEmail())) {
				return $request->expectsJson()
					? abort(403, 'Your email address is not verified.')
					: Redirect::route('verification.notice');
			}
		}
	}

	/**
	 * Behandelt Requests der "business_user" Guard.
	 *
	 * @param string $guard
	 * @param Request $request
	 *
	 * @return IlluminateHttpRedirectResponse|void
	 */
	private function handleGuard_business_user($guard, $request)
	{
		if (Auth::guard($guard)->check()) {
			if (! Auth::guard($guard)->user() ||
			    (Auth::guard($guard)->user() instanceof MustVerifyEmail &&
			     ! Auth::guard($guard)->user()->hasVerifiedEmail())) {
				return $request->expectsJson()
					? abort(403, 'Your email address is not verified.')
					: Redirect::route('business_user.verification.notice');
			}
		}
	}
}

Damit die neue Middleware „EnsureEmailIsVerified“ verwendet wird musst Du diese im Kernel hinterlegen. Öffne die Klasse Kernel im Verzeichnis „app/Http“ und passe die Klasse in der routeMiddleware Property zum Key „verified“ an. Hinterlege hier Deine neue Klasse der Middleware „AppHttpMiddlewareEnsureEmailIsVerified::class“.

/**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array
 */
protected $routeMiddleware = [
	'auth' => AppHttpMiddlewareAuthenticate::class,
	'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
	'bindings' => IlluminateRoutingMiddlewareSubstituteBindings::class,
	'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
	'can' => IlluminateAuthMiddlewareAuthorize::class,
	'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
	'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
	'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
	'verified' => AppHttpMiddlewareEnsureEmailIsVerified::class,
];

Die letzte benötigte Anpassung ist das Error Handling für den „Unauthenticated“ Error und den nachfolgenden Redirect. Dazu fügst Du in der Handler Klasse „app/Exceptions/Handler.php“ folgende Funktion ein:

/**
 * Convert an authentication exception into a response.
 *
 * @param  IlluminateHttpRequest  $request
 * @param  IlluminateAuthAuthenticationException  $exception
 * @return IlluminateHttpResponse
 */
protected function unauthenticated($request, AuthenticationException $exception)
{
	if ($request->expectsJson()) {
		return response()->json(['error' => 'Unauthenticated.'], 401);
	}

	/** @var string $guard */
	$guard = array_get($exception->guards(),0);

	switch($guard) {
		case 'business_user':
			$login = 'business_user.login';
			break;

		default:
			$login = 'login';
			break;
	}

	return redirect()->route($login);
}

Der Login an der Plattform und die Weiterleitungen sollten nun ohne Probleme funktionieren. Im nächsten Schritt schließen wir dieses Tutorial ab. Es fehlt nur noch die Funktion „Passwort vergessen“.

Integration der Passwort vergessen Funktion

Um die Integration des eigenen Benutzertyps abzuschließen, zeige ich Dir in diesem Schritt, wie Du die „Passwort vergessen“ Funktion für Deinen eigenen Benutzertyp integrierst. Dazu musst Du die bestehenden Controller, Views und E-Mail Vorlagen adaptieren. Zuletzt müssen wir noch die neuen Routen bereitstellen.

Beginnen wir nun mit den Views. Kopiere das „passwords“ Verzeichnis mit den Views aus dem Verzeichnis „resources/views/auth“ und füge sie in Deinem View Ordner ein. In meinem Beispiel ist das „resources/vies/businessuser“. Nun passe noch die Routen der Formulare an Deine späteren Routen an.

Dieser Schritt bedarf keiner weiteren Erläuterung. Nachfolgend findest Du aber noch mals die neuen Views.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Reset Password') }}</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    <form method="POST" action="{{ route('business_user.password.email') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Send Password Reset Link') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Reset Password') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('business_user.password.update') }}">
                        @csrf

                        <input type="hidden" name="token" value="{{ $token }}">

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Reset Password') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Nun duplizierst Du die Controller „ForgotPasswordController“, sowie „ResetPasswordController“ und gibt’s ihnen einen neuen Namen. Im neuen „ResetPasswordController“ passt Du den RedirectTo Pfad an und den beiden neuen Controllern die Middleware im Konstruktor. Wir werden in den Controllern mit dem Password Broker von Laravel arbeiten. Dieser verwaltet alles rund um die Passwörter des Benutzers und ist wieder festgelegt auf das User Model.
In den duplizierten Controllern müssen nun einige Funktionen integriert werden um die verwendeten Traits zu überschreiben. Im folgenden findest Du den angepassten „ForgotPasswordController“:

<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use IlluminateFoundationAuthSendsPasswordResetEmails;
use IlluminateSupportFacadesPassword;

class ForgotBusinessUserPasswordController extends Controller
{
	/*
	 |--------------------------------------------------------------------------
	 | Password Reset Controller
	 |--------------------------------------------------------------------------
	 |
	 | This controller is responsible for handling password reset emails and
	 | includes a trait which assists in sending these notifications from
	 | your application to your users. Feel free to explore this trait.
	 |
	 */

	use SendsPasswordResetEmails;

	/**
	 * Create a new controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		$this->middleware('guest:business_user');
	}

	/**
	 * Get the broker to be used during password reset.
	 *
	 * @return IlluminateContractsAuthPasswordBroker
	 */
	protected function broker()
	{
		return Password::broker('business_users');
	}

	/**
	 * Display the form to request a password reset link.
	 *
	 * @return IlluminateHttpResponse
	 */
	public function showLinkRequestForm()
	{
		return view('businessuser.passwords.email');
	}
}

Im neuen „ForgotPasswordController“ wurde der Passwort Namespace von Laravel hinzugefügt, damit wir Zugriff auf den Passwort Broker haben. Danach wurden die beiden Trait-Funktionen „broker“ und „showLinkRequestForm“ überschrieben. Es ist für diesen Schritt übrigens sehr wichtig, dass Du in der „auth“ Konfiguration den Knoten für Passwort mit Deinem neuen User Provider befüllt hast, wie es zu beginn dieses Tutorials erklärt wurde. Ansonsten Kommt es im Broker zu Problemen. Dadurch dass der Passwort Broker nun den korrekten Provider übergeben bekommen hat, kümmert er sich um alle relevenaten Schritte des Passwort-Managements.

Und hier folgt noch der zweite Controller „ResetPasswordController“:

<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use IlluminateFoundationAuthResetsPasswords;
use IlluminateHttpRequest;
use IlluminateSupportFacadesPassword;
use IlluminateSupportFacadesAuth;

class ResetBusinessUserPasswordController extends Controller
{
	/*
	 |--------------------------------------------------------------------------
	 | Password Reset Controller
	 |--------------------------------------------------------------------------
	 |
	 | This controller is responsible for handling password reset requests
	 | and uses a simple trait to include this behavior. You're free to
	 | explore this trait and override any methods you wish to tweak.
	 |
	 */

	use ResetsPasswords;

	/**
	 * Where to redirect users after resetting their password.
	 *
	 * @var string
	 */
	protected $redirectTo = '/business-user/home';

	/**
	 * Create a new controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		$this->middleware('guest:business_user');
	}

	/**
	 * Display the password reset view for the given token.
	 *
	 * If no token is present, display the link request form.
	 *
	 * @param  IlluminateHttpRequest  $request
	 * @param  string|null  $token
	 * @return IlluminateContractsViewFactory|IlluminateViewView
	 */
	public function showResetForm(Request $request, $token = null)
	{
		return view('business_user.passwords.reset')->with(
			['token' => $token, 'email' => $request->email]
		);
	}

	/**
	 * Get the guard to be used during password reset.
	 *
	 * @return IlluminateContractsAuthStatefulGuard
	 */
	protected function guard()
	{
		return Auth::guard('business_user');
	}

	/**
	 * Get the broker to be used during password reset.
	 *
	 * @return IlluminateContractsAuthPasswordBroker
	 */
	protected function broker()
	{
		return Password::broker('business_users');
	}
}

Genauso wie im ersten Controller wurden hier einige Trait-Funktionen überschrieben, damit der korrekte Broker, sowie die korrekte Guard verwendet werden.

Als nächstes betrachten wir die Anpassung der E-Mail Notification. Diese wird nicht über die Controller gesteuert, sondern über den versteckten Trait „CanResetPassword“ der in der User Klasse mit dem Namespace „IlluminateFoundationAuth“ integriert wurde. Deine neuer Benutzertyp verwendet also ebenfalls diesen Trait und verfügt somit über die Funktion „sendPasswordResetNotification“, welche die E-Mail zum zurücksetzten des Passworts verschickt. Leider ist auch diese auf das normale User Model gemünzt, was aber kein Problem darstellt, da wir die Funktion des Traits nun einfach überschreiben.

Erzeuge zunächst eine neue Notification mittels Artisan:

$ php artisan make:notification ResetBusinessUserPasswordNotification

Die Notification passt Du nun folgendermaßen an:

<?php

namespace AppNotifications;

use IlluminateBusQueueable;
use IlluminateSupportFacadesLang;
use IlluminateNotificationsNotification;
use IlluminateContractsQueueShouldQueue;
use IlluminateNotificationsMessagesMailMessage;

class ResetBusinessUserPasswordNotification extends Notification implements ShouldQueue
{
	use Queueable;

	/**
	 * The password reset token.
	 *
	 * @var string
	 */
	public $token;

	/**
	 * The callback that should be used to build the mail message.
	 *
	 * @var Closure|null
	 */
	public static $toMailCallback;

	/**
	 * Create a notification instance.
	 *
	 * @param  string  $token
	 * @return void
	 */
	public function __construct($token)
	{
		$this->token = $token;
	}

	/**
	 * Get the notification's delivery channels.
	 *
	 * @param  mixed  $notifiable
	 * @return array
	 */
	public function via($notifiable)
	{
		return ['mail'];
	}

	/**
	 * Build the mail representation of the notification.
	 *
	 * @param  mixed  $notifiable
	 * @return IlluminateNotificationsMessagesMailMessage
	 */
	public function toMail($notifiable)
	{
		if (static::$toMailCallback) {
			return call_user_func(static::$toMailCallback, $notifiable, $this->token);
		}

		return (new MailMessage)
			->subject(Lang::getFromJson('Reset Password Notification'))
			->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.'))
			->action(Lang::getFromJson('Reset Password'), url(config('app.url').route('business_user.password.reset', $this->token, false)))
			->line(Lang::getFromJson('If you did not request a password reset, no further action is required.'));
	}

	/**
	 * Set a callback that should be used when building the notification mail message.
	 *
	 * @param  Closure  $callback
	 * @return void
	 */
	public static function toMailUsing($callback)
	{
		static::$toMailCallback = $callback;
	}

	/**
	 * Get the array representation of the notification.
	 *
	 * @param  mixed  $notifiable
	 * @return array
	 */
	public function toArray($notifiable)
	{
		return [
			//
		];
	}
}

Die neue Notification nimmt genau wie die Alte einen Token entgegen, über den die Plattform später den Benutzer identifizieren kann. Zusätzlich wird in der Url der korrekte Pfad auf die neue Reset View hinterlegt.

Öffne nun Dein neues User-Model und füge folgende Funktion hinzu:

<?php

namespace App;

use AppNotificationsResetBusinessUserPasswordNotification;
use IlluminateNotificationsNotifiable;
use IlluminateContractsAuthMustVerifyEmail;
use IlluminateFoundationAuthUser as Authenticatable;

class BusinessUser extends Authenticatable implements MustVerifyEmail
{
	use Notifiable;

	protected $guard = 'business_user';

	/**
	 * The attributes that are mass assignable.
	 *
	 * @var array
	 */
	protected $fillable = [
		'name',
		'email',
		'password',
		'company',
		'vat',
		'street_name',
		'street_number',
		'city',
		'zip'
	];

	/**
	 * The attributes that should be hidden for arrays.
	 *
	 * @var array
	 */
	protected $hidden = [
		'password', 'remember_token',
	];

	/**
	 * Versendet eine E-Mail-Verifizierungs Benachrichtigung an den Benutzer
	 */
	public function sendEmailVerificationNotification()
	{
		$this->notify(new NotificationsBusinessUserVerifyEmail);
	}

	/**
	 * Send the password reset notification.
	 *
	 * @param  string  $token
	 * @return void
	 */
	public function sendPasswordResetNotification($token)
	{
		$this->notify(new ResetBusinessUserPasswordNotification($token));
	}
}

Die letzte Änderung, die Du nun noch vornehmen musst, ist die Änderung des Links in Deiner neuen Login-View. Dort ist noch die alte Route zum „Passwort vergessen“ des normalen User-Models hinterlegt. Öffne nun die View und passe die Route darin an:

<a class="btn btn-link" href="{{ route('business_user.password.request') }}">
    {{ __('Forgot Your Password?') }}
</a>

Zusammenfassung

In diesem etwas umfangreicheren Tutorial hast Du gelernt, wie Du Deinen eigenen Benutzertypen ergänzend zum normalen Laravel User Model integrieren kannst. Du hast zu Beginn den Unterschied zwischen einer Benutzerrolle und einem Benutzertypen kennengelernt. Direkt im Anschluss hast Du die normale Laravel Authentication integriert und migriert.
Basierend auf dem normalen User Model hast Du Dein eigenes User Model samt Migration angelegt. Um später mit der Zugriffsbeschränkung und den Controllern arbeiten zu können hast Du in der „auth“ Konfiguration von Laravel Deine Guard, Deinen neuen Provider und die Konfiguration des eigenen Passwort Brokers hinterlegt.
Mittels der „auth“ Middleware von Laravel hast Du erfolgreich Deine Controller vor dem unbefugten Zugriff geschützt und sichergestellt, dass nur Benutzer mit dem neuen Benutzertyp in das zugehörige Backend gelangen.
Im Anschluss an die Zugriffsbeschränkung hast Du entweder die Registrierung Deines neuen Benutzertypes oder das Database Seeding integriert, je nachdem, ob sich Dein neuer Benutzertyp registrieren darf. Wenn Du die Registrierung integriert hast, hast Du ebenfalls gelernt, wie Du Deine eigene E-Mail Verifizierung nach dem Vorbild von Laravel 5.7 integrierst.
Nach der Registrierung oder dem Database Seeding habe ich Dir gezeigt, wie Du Deinen eigenen Login Controller samt Views einbaust. Somit können sich Deine Benutzer des neuen Benutzertypes an Deiner Plattform anmelden.
Last but not least hast Du die Passwort vergessen Funktion integriert und Dich mit dem Passwort Broker von Laravel auseinandergesetzt.

Fragen und Anregungen

Solltest Du Fragen, Anregungen oder Ergänzungen zum Artikel haben, zögere nicht mir diese in einem Kommentar mitzuteilen.