I'm using custom authentication. Added user status(enable/disable) check in retrieveByCredentials function of my custom provider. Now how can I differentiate the error, whether its coming because user enter wrong credentials or because user is disabled?
So far I looked at following function sendFailedLoginResponse, but there is no way to differentiate.
Any suggestions how can I achieve this?
I've approached this in the following way:
/*
LoginController.php
*/
/**
* Override default login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'username';
}
/**
* Override default validation of the user login request.
*
* #param \Illuminate\Http\Request $request
*
* #return void
*/
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => [
'required',
'min:4',
'max:30',
'regex:/^[\S]*$/',
Rule::exists('users')->where(function ($query)
{
$query->where('active', 1);
})
],
'password' => 'required|min:6|max:100'
]);
}
Substituting out whatever the name and expected value of your field is for "active" will enable you to validate depending on whether or not users are active/enabled. You mentioned you've already done this but in this case the validation error will also be the validation message for the "exists" rule. In my application I don't actually need to care why a user failed login but checking the validation message may, I suppose, be enough in your case?
Related
When I deployed my Laravel application to Elasticbeanstalk and then setup the Cloudfront for it, the Validation does not work anymore. It seems like the web page redirect back to the page without error message and the input that user have just put in.
It does work on my local. Here is my code:
class CompanyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'types' => 'required',
'email' => 'required|email',
'url' => 'nullable|url'
];
}
}
Any help is appreciate! Thank you!
I have a Laravel 6 application.
In a typical application, the user would just specify their email, where the email is unique.
However, in my application, I have 2 columns in the User model that is used to authenticate users.
app_id
email
unique(app_id, email)
So in order to login, we need to pass both an app_id and an email, along with the password. The same email could be used across different app_ids.
How would I achieve this?
The default login actions provided by Auth::routes() are the following:
Route::get('login', 'Auth\LoginController#showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController#login');
This is the default login function, part of the AuthenticatesUsers trait used by the LoginController:
/**
* Handle a login request to the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*
* #throws \Illuminate\Validation\ValidationException
*/
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 (method_exists($this, 'hasTooManyLoginAttempts') &&
$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);
}
There are a few ways to approach this.
Option 1: Override login function in LoginController
# app/Http/Controllers/Auth/LoginController.php
public function login(Request $request)
{
// add more stuff like validation, return the view you want, etc.
// This is barebones
auth()->attempt($request->only(['app_id', 'login', 'password']);
}
Option 2: Override both the validateLogin and the credentials functions in LoginController
# app/Http/Controllers/Auth/LoginController.php
protected function validateLogin(Request $request)
{
$request->validate([
'app_id' => 'required|string',
'email' => 'required|string',
'password' => 'required|string',
]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only('app_id', 'email', 'password');
}
I have to authenticate users via an external api (something like ldap) and have been trying to realize authentication via a closure request guard as documented here https://laravel.com/docs/master/authentication#closure-request-guards
It works fine if the user logs in correctly, however on auth failure laravel throws the mentioned error https://laravel.com/docs/master/authentication#closure-request-guards if the failed attempt is returning null from the closure (as it says in the documentation). If it just returns false, laravel doesn't throw an error, however there is no validation feedback.
Auth::viaRequest('ldap', function ($request) {
$credentials = $request->only('login_id', 'password');
if ($user = ExternalLDPAAuth::auth()) {
return $user;
} else {
return null; // laravel throws error
// return false; // <- would not throw error, but no validation
}
}
Is there an easier way to do custom authentication?
I don't really understand the documentation about https://laravel.com/docs/5.7/authentication#authenticating-users
in the end I have to write the guard just like above anyway, right?
You haven't shown the code where you're calling attempt(), but you don't need to use that method when authenticating via the request. You use attempt() when a user attempts to login with credentials and you need to explicitly attempt to authenticate. With request authentication, Laravel will automatically attempt to authenticate as the request is handled, so your code can simply check to see if auth()->check() returns true or not.
In the end I decided to customize the EloquentUserProvider instead of the guard. In the end all i needed was additional logic to validate credentials and retrieve a user by credentials in case the user hadn't logged in yet. I.e. checking the normal eloquent logic first and then checking against the external API if nothing was found (also checking for case of changed password).
class CustomUserProvider extends EloquentUserProvider
{
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
// (...)
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// (...)
}
}
// config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'custom',
],
// (...)
],
// providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('custom', function ($app, array $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
}
I have a laravel project, where I try implement checking if user account is confirmed by activation code from user's email:
public function SignIn(Request $request){
if(Auth::attempt(['email' => $request['email'], 'password' => $request['password']])) {
if(Auth::user()->status==false){
Auth::logout();
Session::flash('activationError','First please active your account');
return back();
}
return redirect()->route('showDashboardWelcome');
}
else{
Session::flash('loginError','Your username and password are wrong');
return back();
}
}
In above code there is function from controller to Sign in user. This function check if the user email and password are correct and if user has confirmed account($status variable from user model with two boolean values) by email code.
If account is not confirmed by user I must logout user from account, because function attempt start simultaneously user's session. Is there any possible way to do this operation without starting a user session and then logout user?
I would be grateful for help. Best regards
Add a middleware to directly query the users table and check the status field. If not verified redirect away to the page showing the message. For example:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckVerified
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = User::where('email', $request->email)->first();
if (!user->status) {
return redirect()->back()->with('not verified');
}
return $next($request);
}
}
You can pass as many parameters to attempt([]) as you want. So here you can add 'status' to the array like this
if(Auth::attempt([
'email' => $request['email'],
'password' => $request['password'],
'status' => true
])) {
}
Every single parameter you pass to that array has to be met for the Auth::attemp() to start the session.
We're inheriting a CakePHP code base, and a schema. Porting it to Laravel 5.2.x, but we have to maintain the schema where they use username in the schema instead of email. So I'm trying to get password reset to work using the username column instead.
I've pulled in ResetPasswords::sendResetLinkEmail into the PasswordController to overwrite it and validate on username instead of email, and pass in username to sendResetLink so the user can be authenticated:
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, [
'username' => 'required|email',
]);
$broker = $this->getBroker();
$response = Password::broker($broker)->sendResetLink(
$request->only('username'), $this->resetEmailBuilder()
);
// ...
But, it throws an error after invoking the method on line ~91 in the PasswordBroker:
$token = $this->tokens->create($user); // line 91
Now I have no idea where this goes since it appears to be invoking an interface that is dependency injected into the PasswordBroker, but it is responsible for saving the password reset record.
Okay, after a lot of fussing around I figured it out. In order to keep the semantic column username instead of using email for password reset:
There are two steps:
1) You need to pull up ResetsPasswords::sendResetLinkEmail into your PasswordController, and have it validate and pass in username to sendResetLink, which is in the question, but also included below for completeness:
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, [
'username' => 'required|email',
]);
$broker = $this->getBroker();
$response = Password::broker($broker)->sendResetLink(
$request->only('username'), $this->resetEmailBuilder()
);
switch ($response) {
case Password::RESET_LINK_SENT:
return $this->getSendResetLinkEmailSuccessResponse($response);
case Password::INVALID_USER:
default:
return $this->getSendResetLinkEmailFailureResponse($response);
}
}
2) The key to this, which seemed to take a while to figure out yet is quite simplistic in nature, but completely undocumented as far as I can tell is changing the trait CanResetPassword::getEmailForPasswordReset used on the User model to also use username instead of email, which you don't want to overwrite within CanResetPassword so I removed the trait from the User model and applied my own copy of it within the application namespace:
namespace App\Traits\Auth;
trait CanResetPassword
{
/**
* Get the e-mail address where password reset links are sent.
*
* #return string
*/
public function getEmailForPasswordReset()
{
return $this->username;
}
}
Now you can send password reset links, and reset passwords. Hope this helps.