Ive just written an observer thats sends an e-mail whenever a user is created.
class UserObserver
{
public function created(User $user)
{
Mail::to($user)->send(new UserAccountCreated(
app('auth.password.broker')->createToken($user),
$user
));
}
}
I ran phpunit to test if my observer works, and it passed. However I was expecting to get an email for each time my tests create a user.
For example:
/** #test */
public function an_admin_can_view_all_clients()
{
$user = factory(User::class)->create(['is_admin' => true]);
$client = factory(Client::class)->create();
$client2 = factory(Client::class)->create();
$this->actingAs($user)->get(route('clients.index'))
->assertSuccessful()
->assertSee($client->name)
->assertSee($client2->name);
}
I would expect an email to be sent when that factory creates the user. But I don't receive one in Mailtrap.
Just wondering if and where laravel disables my observer being triggered when my factory creates that user.
No you have to disable it yourself by using Model::withoutEvents()
For example:
$user = User::first();
User::withoutEvents(function () use ($user) {
$user->delete();
});
Also in this specific case you can also use the Mail fake system provided by Laravel itself
I am developing a Laravel application. My application is using Laravel built-in auth feature. In the Laravel auth when a user registers, a verification email is sent. When a user verifies the email click on the link inside the email, the user has to login again to confirm the email if the user is not already logged in.
VerificationController
class VerificationController extends Controller
{
use VerifiesEmails, RedirectsUsersBasedOnRoles;
/**
* Create a new controller instance.
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
public function redirectPath()
{
return $this->getRedirectTo(Auth::guard()->user());
}
}
I tried commenting on this line.
$this->middleware('auth');
But it's s not working and instead, throwing an error. How can I enable Laravel to be able to verify email even if the user is not logged in?
First, remove the line $this->middleware('auth');, like you did.
Next, copy the verify method from the VerifiesEmails trait to your VerificationController and change it up a bit. The method should look like this:
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
This overrides the method in the VerifiesUsers trait and removes the authorization check.
Security (correct me if I'm wrong!)
It's still secure, as the request is signed and verified. Someone could verify another user's email address if they somehow gain access to the verification email, but in 99% of cases this is hardly a risk at all.
Here's a more future proof solution to the problem:
class VerificationController extends Controller
{
// …
use VerifiesEmails {
verify as originalVerify;
}
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth'); // DON'T REMOVE THIS
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Mark the authenticated user's email address as verified.
*
* #param Request $request
* #return Response
*
* #throws AuthorizationException
*/
public function verify(Request $request)
{
$request->setUserResolver(function () use ($request) {
return User::findOrFail($request->route('id'));
});
return $this->originalVerify($request);
}
}
So when an email confirmation link is clicked by an unauthenticated user the following will happen:
User will be redirected to the login view 1
User enters credentials; logs in successfully 2
User will be redirect back to the email confirmation URL
Email will be marked as confirmed
1 The email will not be marked as confirmed at this point.
2 The user may enter bad credentials multiple times. As soon as he enters the correct credentials he will be redirected to the intended email confirmation URL.
// For Laravel 6 and Above
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
use App\User;
// comment auth middleware
//$this->middleware('auth');
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
Solution to allow email verification for users who are not logged in (i.e. without auth):
Changes to: app/Http/Controllers/Auth/VerificationController.php:
$this->middleware('auth'); to $this->middleware('auth')->except('verify');
Copy verify() method from the VerifiesEmails trait.
Edit verify method to work without expected $request->user() data.
My verify() method in the VerificationController looks like this:
public function verify(\Illuminate\Http\Request $request)
{
$user = User::find($request->route('id'));
if ($request->route('id') != $user->getKey()) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect()->route('login')->with('verified', true);
}
Signed middleware
Laravel uses a middleware named signed to check the integrity of URLs that were generated by the application. Signed checks whether the URL has been changed since it was created. Try changing the id, expiry time or the signature in the url and it will lead to an error - very effective and useful middleware to protect the verify() method
For more information: https://laravel.com/docs/8.x/urls#signed-urls
(Optional)
I redirected my users to the login route, rather than the intended route for two reasons. 1) After login, it would try to redirect the user to the email verification link, leading to an error; 2) I wanted to use the verified true flash data that was attached to the redirect, to show an alert on the login page, if the user had successfully verified their email address.
Example of my login page alert:
#if(session()->has('verified'))
<div class="alert alert-success">Your email address has been successfully verified.</div>
#endif
Suggestions
If you have any suggestions on how I could improve this code, please let me know. I'd be happy to edit this answer.
You should not remove $this->middleware('auth') altogether as that will effect the redirects. If you remove it, the unauthenticated users will be redirected to "/email/verify" instead of "/login"
so $this->middleware('auth'); will be changed to $this->middleware('auth')->except('verify'); in "VerificationController"
Also copy the "verify" function from "VerifiesEmails" into "VerificationController"
add these two lines of code at the top of the function
$user = User::find($request->route('id'));
auth()->login($user);
so you are logging in the user programmatically and then performing further actions
Here's my take on the situation. Verification requires user to login before it can complete the verification, so we can override the verify function and login user using ID we received in the link. It is safe cause verify function is not called if Laravel can't verify the signature from URL so even if someone temper the URL they won't be able to bypass it.
Go to your VerificationController and add the following function at the end of the file.
public function verify(Request $request)
{
if (!auth()->check()) {
auth()->loginUsingId($request->route('id'));
}
if ($request->route('id') != $request->user()->getKey()) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
Note
Make sure you have same_site value in 'config/session.php' set to 'lax'. If it is set to 'strict' then it won't persist session if you were redirected from another site. For example, if you click a verification link from Gmail then your session cookie won't persist, so it won't redirect you to dashboard, but it sets 'email_verified_at' field in the database marking the verification successful. The user won't get any idea what was happened because it will redirect the user to the login page. When you have set it to 'strict', it will work if you copy the verification link directly in the browser address bar but not if the user clicks the link from the Gmail web client because it uses redirect to track the link.
if you want to active user account without login you can do that in 2 steps
1- Remove or comment Auth middleware in VerificationController
Example below:
public function __construct()
{
//$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
2- since verify route passing the {id} you can just edit verify function to find the user by the route id request like code below :
file path : *:\yourproject\vendor\laravel\framework\src\Illuminate\Foundation\Auth\VerifiesEmails.php
$user = User::findOrfail($request->route('id'));
Complete example
public function verify(Request $request)
{
$user = User::findOrfail($request->route('id'));
if (! hash_equals((string) $request->route('id'), (string) $user->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->hasVerifiedEmail()) {
return redirect($this->redirectPath())->with('verified', true);
}
if ($user->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('registered', true);
}
I change EmailVerificationRequest but i now this is wrong, any way it's work.
Warning
This change on the vendor
protected $user;
public function authorize()
{
$this->user = \App\Models\User::find($this->route('id'));
if ($this->user != null){
if (! hash_equals((string) $this->route('id'),
(string) $this->user->getKey())) {
return false;
}
if (! hash_equals((string) $this->route('hash'),
sha1($this->user->getEmailForVerification()))) {
return false;
}
return true;
}
return false;
}
To use inner laravel logic (without overriding the logic), we simply create $request->user() and call trait's verify method. And manually sign in the user when the verification is successful.
use VerifiesEmails {
verify as parentVerify;
}
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!$user) return abort(404);
$request->setUserResolver(function () use($user) {
return $user;
});
return $this->parentVerify($request);
}
public function verified(Request $request)
{
Auth::login($request->user());
}
I want to save the user id,name etc when a successfull login happens.
This is my code,
public function Login(Request $request){
$result1=json_decode(DB::table('tbl_admin_details')
->where('Username',$request->input('username'))
->where('Password',base64_encode($request->input('password')))
->get(),true);
if(count($result1)>0){
return redirect('dashboard');
}else{
return redirect('/');
}
}
when a successfull login happens it will redirect to dashboard, I need to access the user data in that page. how its possible ???
if(count($result1)>0) {
$id= session(['id'=>$result1[0]->id]);
$name= session(['name'=>$result1[0]->name]);
return redirect('dashboard');
}
Then use session()->get('id') and session()->get('name').
in your loginController put method:
protected function authenticated( \Illuminate\Http\Request $request, $user) {
dd(\Auth::user());
}
this method fires up when user is successfully logged in
and gives you logged in user information.
feel free to use it as you want
i am creating session for some purpose but when user logout the purpose value becomes null but i want to use it after user logout.. the scenario is the session is created by admin and i want use this session for normal user but when admin logout its session also become null..
this is the logout code of laravel
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect('/');
}
Here's what you can do:
first get all the data you want to keep.
then delete all the session data.
then save the data in to the session.
then logout.
public function logout(Request $request)
{
// get the data first for example the user's name
$name = Auth::user()->name;
$this->guard()->logout();
$request->session()->invalidate();
// save the data into a new session
session(['name' => $name]);
return redirect('/');
}
then in your view you get the data like so:
#if(session('name'))
{{ session('name') }}
#endif
My middleware is similar to Auth. It checks for a URL pattern (eg: /rest/*), and then looks for token in the request, retrieves its corresponding user from database. After that, I want to save that user in a variable so that I can get back to it later in any of the following controller. What's the best way?
Middleware:
public function handle($request, Closure $next)
{
$token = Input::get("token");
// get user data from database
$user = User::get_user_from_token($token);
// ?? -> How to pass $user to controller, so that ..
return $next($request);
}
In Controller:
public function profile_save() {
// I get the user back here without querying again
$user = ???
}
I would flash the data to the session. When you flash data it only stays there until the next request.
In your middleware add
Session::flash('user', $user);
Don't forget to add this at the top of your middle ware
use Session;
Then whenever you need to access your user use
Session::get('user');
Here is a link to the docs for reference
http://laravel.com/docs/5.0/session#flash-data
I'm using Laravel 5.1.
To pass parameters from the middleware to the controller you can add it to the Request object.
In the middleware:
public function handle($request, Closure $next)
{
$user = 'DB Call To Get User';
$age = 20;
$request->route()->setParameter('user', $user);
$request->route()->setParameter('age', $age);
return $next($request);
}
Then you can get the user in the controller from either the arguments:
public function TestAction(Request $request, User $user, $age)
{}
Or explicitly from the request object:
public function TestAction(Request $request)
{
$user = $request->route()->getParameter('user');
$age = $request->route()->getParameter('age');
}
Of course you can flash the data temporarily to the session or save it to the session itself and set an expiry time, but if you only need it to last for the lifetime of the request then i think this is a good way.
Hope this helps!