Laravel 5.7 email verification expiration time - laravel

I would like to customize the time users have to verify their email address that happens through the built in Auth (since 5.7).
In config/auth there is:
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
But I haven't found anything similar for email verification. There is also no mention in the official documentation.

Whilst the question specifically addresses Laravel 5.7, I feel that it is worth mentioning that as of Laravel 5.8, it is possible to achieve this with a config variable. My search for customising the verification expiration time returned this question as the top result, hence my addition.
If we check out Illuminate\Auth\Notifications\VerifyEmail, the verificationUrl method now looks like this:
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
['id' => $notifiable->getKey()]
);
}
As such, we can just add this block to config/auth.php to customise the time without needing to extend the classes or anything:
'verification' => [
'expire' => 525600, // One year - enter as many mintues as you would like here
],
UPDATE: I've written about the above approach, as well as another on customising the process by overiding the verificationUrl method to give you more flexibility, on my blog.

In deed the options is not there in Laravel, but since laravel makes use of the following:
a trait MustVerifyEmail (in Illuminate\Foundation\Auth\User class extended by the main User model)
Event and Notification
In the MustVerifyEmail trait, there's a method called sendEmailVerificationNotification. This is where the Notification VerifyEmail class referenced by #nakov's answer and its function verificationUrl is used:
/**
* Send the email verification notification.
*
* #return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new Notifications\VerifyEmail);
}
Since we know this, we can do the following:
Extend the Notifications\VerifyEmail to our custom VerifyEmail class
override the implementation of verificationUrl
override the implementation of the sendEmailVerificationNotification method in the User model to use our new VerifyEmail class.
Having done the above, our User model will have the following method:
/**
* Send the email verification notification.
*
* #return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new \App\Services\Verification\VerifyEmail);
}
Now we make use of our custom VerifyEmail class. Then our new VerifyEmail class would look like this:
namespace App\Services\Verification;
use Illuminate\Support\Carbon;
use \Illuminate\Support\Facades\URL;
class VerifyEmail extends \Illuminate\Auth\Notifications\VerifyEmail
{
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify', Carbon::now()->addMinute(3), ['id' => $notifiable->getKey()]
); //we use 3 minutes expiry
}
}
Well, apart from the explanations, the process is quite straight forward. I hope it is easy to grasp. Cheers!

If you open the Illuminate\Auth\Notifications\VerifyEmail::class;
The method that generates the URL already uses expiration time which defaults to 1 hour. Unfortunately there is no option to modify that value.
/**
* Get the verification URL for the given notifiable.
*
* #param mixed $notifiable
* #return string
*/
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()]
);
}

Related

How can i change the password reset URL in a Laravel Project?

In a Laravel project in the folder app/mail there is a file called WelcomeDogSchoolManager.php
In this file I can see the text that is being sent when a new user registers himself.
Within this file, I can see the following code:
#component('mail::button', ['url' => $passwordResetUrl])
Registreren
#endcomponent
Unfortunately, the developer left a mistake in the $passwordResetUrl (leaving it at "https://login..{domain}.nl" instead of the required "https://login.{domain}.nl"
This causes all my users being unable to register (unless they manipulate the URL).
Where in the Laravel Project can I search for the option to change the $passwordResetUrl?
I have no working knowledge of Laravel and am basically just searching through all the files on the server using Filezilla, trying to figure it all out. I got to this point, but have no idea how to proceed further. And since I have 7.200 files left, I don't think I will find it quickly :)
Any tips are appreciated!
PS. I have also found this code. Is this helpful?
$this->passwordResetUrl = url(route('password.reset', [ 'token' => $token, 'email' => $this->user->email, ], false));
Is this helpful?
Full code for the file below
<?php
namespace App\Mail;
use App\Models\DogSchool;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Password;
class WelcomeDogSchoolManager extends Mailable
{
use Queueable;
use SerializesModels;
public $subject = 'Welkom bij de Nederlandse Detectie Bond';
public string $passwordResetUrl;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(public User $user, public DogSchool $dogSchool)
{
$token = Password::getRepository()->create($user);
$this->passwordResetUrl = url(route('password.reset', [
'token' => $token,
'email' => $this->user->email,
], false));
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('emails.welcome_dog_school_manager');
}
}
this
url(route('password.reset', [ 'token' => $token, 'email' => $this->user->email, ], false));
will generate the correct url provided your APP_URL is correct.
In your question, you repeated the same url for the desired and actual so its hard to say exactly what is wrong.

Mocking Dependency Injected URL parameter in PHPUnit

I am trying to create a test for a feature I've written.
The logic is quite simple:
From the api.php I am calling the store method:
Route::group(['prefix' => '/study/{study}/bookmark_list'], function () {
...
Route::post('/{bookmarkList}/bookmark', 'BookmarkController#store');
...
});
thus I am injecting the study and the bookmark list.
My controller passes down the parameters
public function store(Study $study, BookmarkList $bookmarkList)
{
return $this->serve(CreateBookmarkFeature::class);
}
And I am using them in the Feature accordingly
'bookmark_list_id' => $request->bookmarkList->id,
class CreateBookmarkFeature extends Feature
{
public function handle(CreateBookmarkRequest $request)
{
//Call the appropriate job
$bookmark = $this->run(CreateBookmarkJob::class, [
'bookmark_list_id' => $request->bookmarkList->id,
'item_id' => $request->input('item_id'),
'type' => $request->input('type'),
'latest_update' => $request->input('latest_update'),
'notes' => $request->input('notes')
]);
//Return
return $this->run(RespondWithJsonJob::class, [
'data' => [
'bookmark' => $bookmark
]
]);
}
}
I am also using a custom request (CreateBookmarkRequest) which practically verifies if the user is authorised and imposes some rules on the input.
class CreateBookmarkRequest extends JsonRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return $this->getAuthorizedUser()->canAccessStudy($this->study->id);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
"item_id" => ["integer", "required"],
"type" => [Rule::in(BookmarkType::getValues()), "required"],
"latest_update" => ['date_format:Y-m-d H:i:s', 'nullable'],
"text" => ["string", "nullable"]
];
}
}
Now, here comes the problem. I want to write a test for the feature that tests that the correct response is being returned (it would be good to verify the CreateBookmarkJob is called but not that important). The problem is that although I can mock the request, along with the input() method, I cannot mock the injected bookmarkList.
The rest of the functions are mocked properly and work as expected.
My test:
class CreateBookmarkFeatureTest extends TestCase
{
use WithoutMiddleware;
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
// seed the database
$this->seed();
}
public function test_createbookmarkfeature()
{
//GIVEN
$mockRequest = $this->mock(CreateBookmarkRequest::class);
$mockRequest->shouldReceive('authorize')->once()->andReturnTrue();
$mockRequest->shouldReceive('rules')->once()->andReturnTrue();
$mockRequest->shouldReceive('input')->once()->with('item_id')->andReturn(1);
$mockRequest->shouldReceive('input')->once()->with('type')->andReturn("ADVOCATE");
$mockRequest->shouldReceive('input')->once()->with('latest_update')->andReturn(Carbon::now());
$mockRequest->shouldReceive('input')->once()->with('notes')->andReturn("acs");
$mockRequest->shouldReceive('bookmark_list->id')->once()->andReturn(1);
//WHEN
$response = $this->postJson('/api/recruitment_toolkit/study/1/bookmark_list/1/bookmark', [
"type"=> "ADVOCATE",
"item_id"=> "12",
"text"=> "My first bookmark"
]);
//THEN
$this->assertEquals("foo", $response['data'], "das");
}
One potential solution that I though would be to not mock the request, but this way I cannot find a way to mock the "returnAuthorisedUser" in the request.
Any ideas on how to mock the injected model would be appreciated, or otherwise any idea on how to properly test the feature in case I am approaching it wrong.
It is worth mentioning that I have separate unit tests for each of the jobs (CreateBookmarkJob and RespondWithJSONJob).
Thanks in advance
A feature test, by definition, will be imitating an end-user action. There's no need to mock the request class, you just make the request as a user would.
Assuming a Study with ID 1 and a BookmarkList with ID 1 have been created by your seeder, Laravel will inject appropriate dependencies via route model binding. If not, you should use a factory method to create models and then substitute the appropriate ID in the URL.
<?php
namespace Tests\Feature;
use Tests\TestCase;
class CreateBookmarkFeatureTest extends TestCase
{
use WithoutMiddleware;
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->seed();
}
public function TestCreateBookmarkFeature()
{
$url = '/api/recruitment_toolkit/study/1/bookmark_list/1/bookmark';
$data = [
"type"=> "ADVOCATE",
"item_id"=> "12",
"text"=> "My first bookmark"
];
$this->postJson($url, $data)
->assertStatus(200)
->assertJsonPath("some.path", "some expected value");
}
}
I agree with #miken32's response - that a feature should indeed imitate a user interaction - however the dependency injection via route model binding still did not work.
After spending some hours on it, I realised that the reason for it is that
use WithoutMiddleware;
disables all middleware, even the one responsible for route model binding, therefore the object models were not injected in the request.
The actual solution for this is that (for laravel >=7) we can define the middleware we want to disable, in this case:
$this->withoutMiddleware(\App\Http\Middleware\Authenticate::class);
Then we just use
$user = User::where('id',1)->first(); $this->actingAs($user);
And everything else works as expected.
DISCLAIMER: I am not implying that miken32's response was incorrect; it was definitely in the right direction - just adding this as a small detail.

Is there a way to catch when I'm using a laravel tinker session to fiddle with the Models in an Observer class?

I have an Observer set up to Listen to a Model's events in order to keep my Controller clean of Logging messages. My implementation is as follows:
First, a store method that does just what it's supposed to do. Create and save a new model from valid parameters.
# app/Http/Controllers/ExampleController.php
namespace App\Http\Controllers;
use App\Http\Requests\StoreExample;
use App\Example;
class ExampleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
/**
* Create and save an Example from validated form parameters.
* #param App\Http\Requests\StoreExample $request
*/
public function store(StoreExample $request)
{
Example::create($request->validated());
return back();
}
}
The StoreExample Form Request isn't important. It just validates and checks a gate to authorize the action.
The Observer I have set up logs this action.
# app/Observers/ExampleObserver.php
namespace App\Observers;
use App\Example;
class ExampleObserver
{
public function created(Example $example): void
{
\Log::info(auth()->id()." (".auth()->user()->full_name.") has created Example with params:\n{$example}");
}
}
The problem I have, is the way my logs depend on the auth() object to be set. Given the auth middleware and the gate it has to check in order to store an Example, there is no way a guest user will set off this code.
However, I do like to use tinker in my local and staging environments to check the behavior of the site but that can set off an error (Well, PHP notice to be more precise) because I can create Example models without being authenticated and the logger will try to fetch the property full_name from the non-object auth()->user().
So my question is as follows: Is there a way to catch when I'm specifically using the Laravel tinker session to handle my models in the Observer class?
Okay, replying to my own question: There IS a way. It requires using a Request object. Since observers do not deal with requests on their own, I injected one in the constructor. request() can be used instead, so no DI is needed.
Why is a Request important?
Because a request object has an accessible $server attribute that has the information I want. This is the relevant information I get by returning a dd($request->server) (I'm not gonna paste the whole thing. My Request's ServerBag has over 100 attributes!)
Symfony\Component\HttpFoundation\ServerBag {#37
#parameters: array:123 [
"SERVER_NAME" => "localhost"
"SERVER_PORT" => 8000
"HTTP_HOST" => "localhost:8000"
"HTTP_USER_AGENT" => "Symfony" // Relevant
"REMOTE_ADDR" => "127.0.0.1"
"SCRIPT_NAME" => "artisan" // Relevant
"SCRIPT_FILENAME" => "artisan" // Relevant
"PHP_SELF" => "artisan" // Relevant
"PATH_TRANSLATED" => "artisan" // Relevant
"argv" => array:2 [ // Relevant
0 => "artisan"
1 => "tinker"
]
"argc" => 2
]
}
So there's all these attributes I can filter by using $request->server('attribute') (returns $request->server->attribute or null, so no risk of accessing an undefined property). I can also do $request->server->has('attribute') (returns true or false)
# app/Observers/ExampleObserver.php
namespace App\Observers;
use App\Example;
class ExampleObserver
{
/* Since we can use request(), there's no need to inject a Request into the constructor
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
*/
public function created(Example $example): void
{
\Log::info($this->getUserInfo()." has created Example with params:\n{$example}");
}
private function getUserInfo(): string
{
// My logic here.
}
}

Updating and Validating email field

Im creating a update page, where the user can change his email, but it needs a password confirmation for that. But before this, it needs some kind of validation, first to check if the current email is correct and also if the new email is available to be saved, and after the password is correct than be updated.
But im having some trouble in making the request, validation, can someone tell me if this is correct? (dont mind the dd i putted, is just for testing).
$user = Auth::user();
$this->validate($request, array(
'current_email' => 'required|email|unique:users,email,'. $user->id,
'email' => 'required|email|unique:users',
'verify_password' => 'required|min:6'
));
//Verify information user before updating
if($user->email != $request->current_email){
dd("Current Email is not the same");
}
if($user->password != bcrypt($request->verify_password)){
dd("Password incorrect, will not update");
}
dd("update, is ok now");
First write this in your console.
php artisan make:provider ValidationServiceProvider
Then replace your app\Providers\ValidationServiceProvider with
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class ValidationServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot() {
Validator::extend('old_password', function($attribute, $value, $parameters, $validator) {
return auth()->validate([
'email' => auth()->user()->email,
'password' => $value
]);
});
}
/**
* Register the service provider.
*
* #return void
*/
public function register() {
//
}
}
Now add it to providers in config\app.php, like
App\Providers\ValidationServiceProvider::class,
Now replace your method definition with
$user = auth()->user();
$validator = Validator::make($request, array(
'current_email' => 'required|email|exists:users,email,id,'. $user->id,
'email' => 'required|email|unique:users',
'verify_password' => 'required|min:6|old_password'
));
if($validator->fails()) {
return redirect()->back()->withErrors($validator)->withInput();
}
dd("Good to go!!!");
First of all I have replaced your current_email unique validation with exists. Why? Have a look here
The method I have used here for validation is called Custom Validation. More details here
Let me know if you face any issues :)
What you have will work, but there are a couple of things that I would recommend.
First, you already have the $request, so you should get the user from that. While Auth::user() and $request->user() do return the same thing, the later will not require the use of a facade and therefore is a little quicker.
Second, I would validate the before you validate the request body. It doesn't make sense to spend the resources validating the $request if the password is not correct.
Third, you can put your $user->email == $request->current_email check in the validation using the exists rule. It would be something like "exists:users,email,id,$user->id".
How you display the errors will be up to how the request is being done. Take a look at the Displaying Validation Errors section for submitting a form and the AJAX Requests and Validation for AJAX requests.

Laravel 5.1 multiple authentication

How can you authenticate multiple types of users in Laravel 5.1 e.g. Jobseeker, Recruiter, Admin etc.
Some of you have suggested using a single users table to store only the password and email, creating profile tables to store user specific information (jobseeker_profile, recruiter_profile) and using roles to differentiate between the different types of users (i.e having a roles and role_user) table.
This is all very well but then what if the different types of users have different registration and login forms. How do you customize the default auth controller out of the box to display the correct view?
So if I have the following routes:
// Jobseeker Authentication routes...
Route::get('auth/login', 'Auth\AuthController#getLogin');
Route::post('auth/login', 'Auth\AuthController#postLogin');
Route::get('auth/logout', 'Auth\AuthController#getLogout');
// Jobseeker Registration routes...
Route::get('auth/register', 'Auth\AuthController#getRegister');
Route::post('auth/register', 'Auth\AuthController#postRegister');
// Recruiter Authentication routes...
Route::get('recruiter/auth/login', 'Auth\AuthController#getLogin');
Route::post('recruiter/auth/login', 'Auth\AuthController#postLogin');
Route::get('recruiter/auth/logout', 'Auth\AuthController#getLogout');
// Recruiter Registration routes...
Route::get('recruiter/auth/register', 'Auth\AuthController#getRegister');
Route::post('recruiter/auth/register', 'Auth\AuthController#postRegister');
This is the default auth controller out of the box:
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers;
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
traits used by the default out of the box auth controller:
trait AuthenticatesUsers
{
use RedirectsUsers;
public function getLogin()
{
return view('auth.login');
}
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $this->getCredentials($request);
if (Auth::attempt($credentials, $request->has('remember'))) {
return redirect()->intended($this->redirectPath());
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => $this->getFailedLoginMessage(),
]);
}
public function loginPath()
{
return property_exists($this, 'loginPath') ? $this->loginPath : '/auth/login';
}
}
trait RegistersUsers
{
use RedirectsUsers;
public function getRegister()
{
return view('auth.register');
}
public function postRegister(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::login($this->create($request->all()));
return redirect($this->redirectPath());
}
}
I'm sure this is a very common requirement for many web applications but I can't find any helpful tutorials for Laravel specific implementations. All the tutorial simply focus on the out of the box implementation for some odd reason.
Any help on the above would be much appreciated.
This is not a solution to your question directly, but alternative way to solve your question problem with.
In stead of creating different username and password for different groups, make a central authentication that has roles. It called user and roles.
You can define groups with different roles, and each roles has specific access to respective area.
Regarding registration process you can make two differnet views but using the same controller, and for each view you can create a hidden field to indicate if it is jobseekers group or recruiter group.
Both will receive two different confirmation emails where they should fill the rest of the profile information, like recruiter should put company name and jobseeker should put his name etc. they might have two different tables for profile information, but still using the same login system.
By adding condition to middleware and correct route, if jobseeker tries to access recruiter area even if jobseeker is logged in the system, the jobseeker won't be able to access that area or the opposite way.
Since Laravel 5.1 has build in user login system, so you have few choices, build your own roles or use 3rd party.
I suggest you to build your own so you have control over your code and can further develop it as you wish with time. It might take you half day to get it run and understand how it works, but it is worth spending that time with the right approach in stead of the way you go in your Question OR using 3rd party is fine too, there is a lot of packages around you can search for. I have personally used Entrust (https://github.com/Zizaco/entrust) it is easy and nice way to provide roles and permissions to your project.
Here is also a link to video developed by Jeffrey Way at Laracast, it builds user and roles system from scratch for Laravel 4. but since you have user part, just follow roles part and with small modifications you will have a roles system to your Laravel 5.1, I have tried it and it works.
Regarding your question in the comments, when you follow the video you will understand the concept.
Link to the video: https://laracasts.com/lessons/users-and-roles
You might need to create account to see the video, most of videos are free.
Good practice
It is always also a good practice to illustrate what you want to achieve that makes things easier, I have just made an example for your project, but that is only example for learning:
I encourage you to read some of the topics regarding roles, here you will also find some inspiration to 3rd party acl systems to Laravel, there might be more articles but here is some:
Reading:
https://laracasts.com/discuss/channels/laravel/which-package-is-best-for-roles-permissions/?page=2
https://laracasts.com/discuss/channels/general-discussion/laravel-5-user-groups-management
https://laracasts.com/discuss/channels/general-discussion/roles-and-permissions-in-laravel-5
EDIT
Important Note
Laravel 5.1 has introduced Authorization, I have not found much documentation online yet but it is worth to spend some time learning it:
http://laravel.com/docs/5.1/authorization#policies
NEW UPDATE
There are some great videos solution for what you asking, follow ACL parts here
https://laracasts.com/series/whats-new-in-laravel-5-1
This might be very interesting too:
https://laracasts.com/lessons/email-verification-in-laravel
This will give you a complete own developed solution.
You can achieve multiple authentication easily by pulling up the sarav/laravel-multiauth package
composer require sarav/laravel-multiauth
I assume you have separate tables for Jobseeker, Recruiter, Admin.
Step 1 : Open app.php and replace
Illuminate\Auth\AuthServiceProvider::class
with
Sarav\Multiauth\MultiauthServiceProvider::class
Then, open up auth.php file and remove
<?php
return [
'driver' => 'eloquent',
'model' => 'App\User::class',
'table' => 'users',
'password' => [
'email' => 'emails.password',
'table' => 'password_resets',
'expire' => 60,
],
];
and add the following code
return [
'multi' => [
'jobseeker' => [
'driver' => 'eloquent',
'model' => App\Jobseeker::class, // Model Class
'table' => 'jobseeker' // jobseeker table
],
'recruiter' => [
'driver' => 'eloquent',
'model' => App\Recruiter::class, // Model Class
'table' => 'recruiter' //recruiter table
],
'admin' => [
'driver' => 'eloquent',
'model' => App\Admin::class, // Model Class
'table' => 'admin' //admin table
],
],
'password' => [
'email' => 'emails.password',
'table' => 'password_resets',
'expire' => 60,
]
];
Thats it!
Now you can try login attempt by calling
\Auth::attempt('jobseeker', ['email'=> 'johndoe#example.com', 'password' => 'secret']);
\Auth::attempt('recruiter', ['email'=> 'johndoe#example.com', 'password' => 'secret']);
\Auth::attempt('admin', ['email'=> 'johndoe#example.com', 'password' => 'secret']);
Always remember first paramter should be your user parameter. Here I have given jobseeker for jobseeker login attempt, recruiter for recruiter attempt and admin for admin login attempt. Without the proper first parameter system will throw exception.
For more detailed information checkout this article
http://sarav.co/blog/multiple-authentication-in-laravel-continued/
Short Answer: Add user types to your users table with specific number.
TL;DR answer.
Long Answer:
If you have migrated your table, just run php artisan migrate:rollback.
Add following line to your migration table for users:
$table->integer("user_type")->default(0);
Here I am considering that user type zero is just a simple JobSeeker.
And in your form, you can add option with value zero and one such that people will be selecting what they want to be like recruiter. There is no need of other
As another solution, i can suggest you to use a polymorphic relation between User and Account, like
class User extends Eloquent {
...
public function account() {
return $this->morphTo();
}
}
class Account extends Eloquent {
...
public function user() {
return $this->morphOne(App\User::class, 'account');
}
}
class JobSeeker extends Account { ... }
class Recruiter extends Account { ... }
For different types of Account, you can use route prefixes and different auth controllers, specially for registration who differs for each account instances :
// Recruiter Authentication routes...
Route::group(['prefix' => 'recruiter'], function() {
Route::controller('auth', 'Auth\RecruiterAuthController');
});
At last, you can access the authenticated account directly from auth()->user()->account. it will return any instance of Account (Recruiter, Admin, ....)
hope it helps you ;)
I will try to explain how authentication is managed in Laravel 5.1
On application start AuthServiceProvider is called, which calls registerAuthenticator() function in which new AuthManager is created.
AuthServiceProvider -> registerAuthenticator() -> new AuthManager()
On manager create createNameDriver function will be called in which new nameProvider will be created, where name is your auth driver selected in auth config file. Then in that function new Guard will be created and nameProivder will be passed to its contractor. All auth functions in that Guard will use functions from that provider to manage auth. Provider implements UserProvider which has
retrieveById($identifier),
retrieveByToken($identifier, $token),
updateRememberToken(Authenticatable $user, $token),
retrieveByCredentials(array $credentials),
validateCredentials(Authenticatable $user, array $credentials)
functions.
Main idea of managing multi auth in Laravel 5.1 is to create new AutServiceProvider and on its boot pass app auth new AuthModelProvider which functions then will be used in same Guard. In AuthModelProvider you can manage all retrieve functions the way you need.
Here is all changed I've made to manage multi auth. My project name is APC, that's why I use it everywhere.
Add this function to your models
public function getAuthIdentifier()
{
return [self::MODULE_NAME => $this->getKey()];
}
Create AuthServiceProvider in Provider/YourProjectName directory. In boot function we extend auth from our new provider AuthModelProvider.
<?php
namespace App\Providers\Apc;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Hashing\BcryptHasher;
class AuthServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
self::getAuthModels();
$this->app['auth']->extend('apc', function() {
return new AuthModelProvider(self::getAuthModels(), new BcryptHasher());
});
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
}
public static function getAuthModels()
{
$configModels = config('auth.models');
$authModels = [];
foreach ($configModels as $key => $class) {
$authModel = new $class();
$authModels [$key]= $authModel;
}
return $authModels;
}
}
Create AuthModelProvider in same directory. Diff in my models is existence of login field in company table. But you can be more specific if you want. In retrieveByCridentials function I just look for existence of login and choose my model accordingly.
<?php
namespace App\Providers\Apc;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Support\Str;
class AuthModelProvider implements UserProvider
{
protected $users;
protected $hasher;
public function __construct($usersModels, HasherContract $hasher)
{
$this->users = $usersModels;
$this->hasher = $hasher;
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifiers)
{
foreach ($identifiers as $key => $id) {
if (isset($this->users[$key])) {
return $this->users[$key]->where('id', $id)->active()->base()->first();
}
}
}
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* #param mixed $identifier
* #param string $token
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifiers, $token)
{
return null;
$user = $this->getUserByIdentifier($identifiers);
if ($user) {
return $user->where($user->getRememberTokenName(), $token)->active()->first();
}
}
/**
* Update the "remember me" token for the given user in storage.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $token
* #return void
*/
public function updateRememberToken(Authenticatable $user, $token)
{
$user->setRememberToken($token);
$user->save();
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return null;
}
if (isset($credentials['login'])) {
$userModel = $this->users['company'];
} else {
$userModel = $this->users['user'];
}
$query = $userModel->newQuery();
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
private function getUserByIdentifier($identifiers)
{
if (!$identifiers) {
}
foreach ($identifiers as $namespace => $id) {
if (isset($this->users[$namespace])) {
return $this->users[$namespace];
}
}
return null;
}
}
Add AuthServiceProvider to app conf file.
\App\Providers\Apc\AuthServiceProvider::class,
Make this changes to auth conf file.
'driver' => 'apc',
'models' => [
\App\Apc\User\User::MODULE_NAME => \App\Apc\User\User::class,
\App\Apc\Company\Company::MODULE_NAME => \App\Apc\Company\Company::class
],
That's all. Hope it was helpful.

Resources