Authentication on username instead of email - laravel

I want to check the user on username and password instead of email and password. And I found that this works:
public function login(){
if(!Auth::attempt(request()->only('username', 'password')))
{
return redirect('login');
}
return redirect('login');
}
But I liked how the original login() method gave the error 'These credentials do not match our records.'.
I would like to know if this is the "Laravel way" to doing this because I'd think it would be a one line code to change it to username. If this is the right way how would I get the error back to show up?

Instead of that I removed the login method and added this to LoginController.php:
/**
* Override the username method used to validate login
*
* #return string
*/
public function username()
{
return 'username';
}
That was all there is to it it works great!

Related

how to change redirect after auth Email verification in laravel 8?

I have 2 condition after successful registration with email verification.
If the new user is select plan from home page, redirects to registration page submits the form. then will get Email verfication link, and after email verified I want to redirect directly to checkout. Plan id will be saving session , so I can get all the details of plan.
If the new user is not select plan from home page, then he can sign up and redirects to dashboard
But in laravel after email verfication always redirects to home page. But I dont want to redirect to home page again.
How can be this done? Wher can do the coding part?
Verification Controller
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* #var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* 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');
}
protected function verified(Request $request)
{
$request->session()->flash('alert','Your Email is verfied');
}
Routes
public function emailVerification()
{
return function () {
$this->get('email/verify', 'Auth\VerificationController#show')->name('verification.notice');
$this->get('email/verify/{id}/{hash}', 'Auth\VerificationController#verify')->name('verification.verify');
$this->post('email/resend', 'Auth\VerificationController#resend')->name('verification.resend');
};
}
Add a method called redirectTo(). It will be called if it exists.
public function redirectTo()
{
// put your routing logic here
}
The function should return a string of the url to go to.
The best option that worked for me in Laravel 9* was to define a new public constant in the RouteServiceProvider right under or above the HOME
e.g
Location: app\Providers\RouteServiceProvider
public const SUB = '/account/subscription';
Then go to VerificationController
Location: app\Http\Controllers\Auth\VerificationController
and change
protected $redirectTo = RouteServiceProvider::HOME;
to
protected $redirectTo = RouteServiceProvider::SUB;
If you are using Fortify, I think the more Laravel way to solve this, is to create a Response handler.
Laravel will, when an email is verified, generate a VerifyEmailResponse. So to modify that logic, you would create a class and make that the singleton.
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\VerifyEmailResponse as VerifyEmailResponseContract;
class VerifyEmailResponse implements VerifyEmailResponseContract
{
public function toResponse($request)
{
// Your custom logic returning redirect(), \Illuminate\Routing\Redirector or \Illuminate\Http\RedirectResponse
}
}
And in your FortifyServiceProvider class:
use Laravel\Fortify\Contracts\VerifyEmailResponse as VerifyEmailResponseContract;
public function boot()
{
// logic
$this->app->singleton(VerifyEmailResponseContract::class, VerifyEmailResponse::class);
// logic
}
Couple approaches from what I can see...
In your controller actions where you are routing them to
Auth\VerificationController#verify do the check to make sure a flag
isn't already set, set the flag if it isn't set, put your redirect
after that logic. So it would be something like this for a route...
return redirect()->route('route.name', [$veriYouWantToSendAlong]);
Or to a static page with errors.
return redirect()->route('page/name')->withErrors(['Some error here']);
In your Laravel auth controller should be a protected $redirectTo = '/home'; action you can change that to dashboard or wherever you'd like to send them after log in.
Maybe add this in the Routes/web.php.
And change the /dashboard to whatever you like :).
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
$request->fulfill();
return redirect('/dashboard');
})->middleware(['auth', 'signed'])->name('verification.verify');

Laravel custom Login Throttling

I want to add Login Throttling to my custom login code. I use an email and a password for the login. Here is what i did:
I added
use ThrottlesLogins;
protected $maxLoginAttempts=3;
protected $lockoutTime=20;
at the beginning of my class, and then in my login method i did:
public function login(Request $request)
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
If the login fails i add
$this->incrementLoginAttempts($request);
if it succeed I add
$this->clearLoginAttempts($request);
The problem though i'm getting:
Undefined method LoginController::username
I'm guessing it is because i use an email instead of a username for login. What is the solution for this?
This particular trait, ThrottlesLogins, is meant to be used for the default LoginController that uses the AuthenticatesUsers trait, which provides a username method.
You can define this method yourself:
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'email';
}

How to Verify Email Without Asking the User to Login to Laravel

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());
}

Laravel Login and save user details

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

How to redirect intended user to a different route based on their role?

I'd like to redirect my user to different route, based on their role. I have two secured area in my app, "admin" and "dashboard". I'd like to check if user is authenticated, then redirect to intended, but if user has role editor it should be redirected to dashboard, otherwise if he has role admin should be redirected to admin area.
I'm using AuthenticatesAndRegistersUsers class in my login. I have this on my custom controller:
/**
* The default redirecTo path.
*
*/
protected $redirectTo = '/dashboard';
So when a user is authenticated it will be redirected to dashboard, but I'd like to check if the intended url is on admin group route and if user has admin role it should be redirected to admin area.
I'm using this middleware to redirect to login:
public function handle($request, Closure $next)
{
if ($this->auth->guest())
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('auth/login');
}
}
return $next($request);
}
You could overwrite the redirectPath method used by the trait in your AuthController to inject the logic you need. Something like this:
/**
* Get the post register / login redirect path.
*
* #return string
*/
public function redirectPath()
{
// Logic that determines where to send the user
if (\Auth::user()->type == 'admin') {
return '/admin';
}
return '/dashboard';
}
EDIT:
Laravel uses the following declaration in the AuthenticatesAndRegistersUsers trait to redirect the user after successful login:
return redirect()->intended($this->redirectPath());
This will try to redirect the user to the previously attempted URL.
If you need to redirect users to the right place when they're already logged in, that would be best done by adding more logic to your authentication middleware.
Another approach is to override authenticated method
public function authenticated()
{
if(Auth::check()) {
if(\Auth::user()->hasRole('Super Admin')) {
return redirect('/admin-dashboard');
} else {
return redirect('/user-dashbaord');
}
}
}
I use this one. You also need to modify middleware RedirectIfAuthenticated so that it won't route home. Just to different user's dashboard.
public function authenticated()
{
if($request->user()->hasRole('admin'))
{
// return redirect()->intended(route('admin.index'));
return redirect()->route('admin.index');
}
if($request->user()->hasRole('super'))
{
return redirect()->route('super.index');
}
if($request->user()->hasRole('officer'))
{
return redirect()->route('officer.index');
}
}

Resources