I'm creating a login function in Laravel 5.4 and I want to show error message in the view when the password is incorrect. Also I have a custom message for account approval so it makes things a bit difficult for me. Meanwhile I put those messages together but is not very user-friendly. How can I separate them?
This is my controller:
public function login(Request $request)
{
// validate the form data
$this->validate($request, [
'email' => 'required|email|exists:users,email',
'password' => 'required|min:6'
]);
// attempt to log
if (Auth::attempt(['approve' => '1', 'email' => $request->email, 'password' => $request->password ], $request->remember)) {
// if successful -> redirect forward
return redirect()->intended(route('user.overview'));
}
// if unsuccessful -> redirect back
return redirect()->back()->withInput($request->only('email', 'remember'))->withErrors([
'approve' => 'Wrong password or this account not approved yet.',
]);
}
As result i want to replace Wrong password or this account not approved yet with two separate messages:
If password is wrong to show: Password is wrong
If account not approved show: This account not approved yet
You can pass custom error messages for each validation rule, you can do this:
public function login(Request $request)
{
//Error messages
$messages = [
"email.required" => "Email is required",
"email.email" => "Email is not valid",
"email.exists" => "Email doesn't exists",
"password.required" => "Password is required",
"password.min" => "Password must be at least 6 characters"
];
// validate the form data
$validator = Validator::make($request->all(), [
'email' => 'required|email|exists:users,email',
'password' => 'required|min:6'
], $messages);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
} else {
// attempt to log
if (Auth::attempt(['approve' => '1', 'email' => $request->email, 'password' => $request->password ], $request->remember)) {
// if successful -> redirect forward
return redirect()->intended(route('user.overview'));
}
// if unsuccessful -> redirect back
return redirect()->back()->withInput($request->only('email', 'remember'))->withErrors([
'approve' => 'Wrong password or this account not approved yet.',
]);
}
}
Before this, you have to include Validator class:
use Illuminate\Support\Facades\Validator;
Without writing a new custom login method we can easily handle a custom wrong password message with the Auth default login process.
Open LoginController from the location: app/Http/Controllers/Auth/
Include the Request class if not exit on top of the controller
use Illuminate\Http\Request;
Finally add below line of codes at the very bottom of your LoginController to process the response error with custom message
/**
* Get the failed login response instance.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse
*/
protected function sendFailedLoginResponse(Request $request)
{
$errors = [$this->username() => trans('auth.failed')];
// Load user from database
$user = \App\User::where($this->username(), $request->{$this->username()})->first();
if ($user && !\Hash::check($request->password, $user->password)) {
$errors = ['password' => 'Wrong password'];
}
if ($request->expectsJson()) {
return response()->json($errors, 422);
}
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors($errors);
}
You can use like this:
return Redirect::back()->withInput(Input::all());
If you're using Form Request Validation, this is exactly how Laravel will redirect you back with errors and the given input.
Excerpt from \Illuminate\Foundation\Validation\ValidatesRequests:
return redirect()->to($this->getRedirectUrl())
->withInput($request->input())
->withErrors($errors, $this->errorBag());
Controller:
public function login(Request $request)
{
// validate the form data
$this->validate($request, [
'email' => 'required|email|exists:users,email',
'password' => 'required|min:6'
]);
// attempt to log
if (Auth::attempt(['approve' => '1', 'email' => $request->email, 'password' => $request->password ], $request->remember)) {
// if successful -> redirect forward
return redirect()->intended(route('user.overview'));
}
// if unsuccessful -> redirect back
return Redirect::back()
->withInput()
->withErrors(
[
'password' => 'Wrong Password',
],
[
'approve' => 'Account not approved',
],
);
}
Related
I am trying to redirect users to their dashboard after successful login and if credentials doesn't exists or match the database records it should return laravel's default error messages.
It redirects to dashboard view if successful, but when an errors occurs, it doesn't redirect me back to the login, instead it shows a blank page.
below is my code
public function login(Request $request)
{
$message = array(
'required.email' => 'This is required',
'required.password' => 'This is required',
);
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:8',
]);
$email = $request->email;
$pass = $request->password;
if (Auth::guard('guest')->attempt([
'email' => $email,
'password' => $pass
], $request->get('remember'))) {
return redirect()->route('dashboard');
} else {
return back()->withInput($request->only('email', 'remember'));
}
}
this should suffice your need
return redirect('dashboard')->withErrors('Something went wrong!');
I have three types of Authenticatable model and I need to have separate JWT authentication for each. Let me explain more about my issue.
I'm using MongoDB as my database and Laravel MongoDB is the package that I use.
User, Admin, and ServiceProvider are my models.
To having JWT auth in Laravel I use jwt-auth package. It's ok with user model (collection). when I want to use JWT with any of other models It not work and do everything with user again.
I search a lot an I found out that to change the provider user model I can use Config::set(); method like below,
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
But no effect on JWT auth. (I checked the value of 'jwt.user' and 'auth.providers.users.model' with Config::get() method and returned it, It has been changed to 'App\Admin').
Need to say, My codes are as simple as possible according to the documentation of the package.
Here is my UserController code:
class UserController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$credentials = $request->only('email', 'password');
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}
$user = User::where('email', $request->email)->first();
return response()->json([
'user' => $user,
'token' => $token
]);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255|unique:users',
'phone' => 'required|valid_phone|unique:users',
'password' => 'required|min:6',
'first_name' => 'required',
'last_name' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
User::create([
'phone' => $request->get('phone'),
'first_name' => $request->get('first_name'),
'last_name' => $request->get('last_name'),
'city_abbr' => $request->get('city_abbr'),
'email' => $request->get('email'),
'password' => bcrypt($request->get('password')),
]);
$user = User::first();
$token = JWTAuth::fromUser($user);
return response()->json([
'user' => $user,
'token' => $token
]);
}
}
And my AdminController:
class AdminController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$credentials = $request->only('email', 'password');
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}
$admin = Admin::where('email', $request->email)->first();
return response()->json([
'admin' => $admin,
'token' => $token
]);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255|unique:admins',
'phone' => 'required|valid_phone|unique:admins',
'password' => 'required|min:6',
'name' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$admin = Admin::create([
'phone' => $request->get('phone'),
'name' => $request->get('name'),
'access' => $request->get('access'),
'email' => $request->get('email'),
'password' => bcrypt($request->get('password')),
]);
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
$token = JWTAuth::fromUser($admin);
return response()->json([
'admin' => $admin,
'token' => $token
]);
}
}
Am I wrong in somewhere?
Is there any solution for this?
Update:
To be sure about MongoDB functionality, I test all of above doings with a relational database, actually MySQL. Nothing changed!
JWTAuth generates token but when I run toUser method with any models except User, it returns null!
Any solution will be appreciated.
Here is what you must fo to add multi auth ability with JWT to my project.
In tymon JWT auth package. In JWTAuthServiceProvider, Change Tymon\JWTAuth\JWTAuth and Tymon\JWTAuth\Providers\User\UserInterface definition type from singleton to bind in bootBindings method.
Defined a new middleware and below code is its handle method:
public function handle($request, Closure $next){
if (!$request->header('Auth-Type')) {
return response()->json([
'success' => 0,
'result' => 'auth type could not found!'
]);
}
switch ($request->header('Auth-Type')) {
case 'user':
$auth_class = 'App\User';
break;
case 'admin':
$auth_class = 'App\Admin';
break;
case 'provider':
$auth_class = 'App\ServiceProvider';
break;
default:
$auth_class = 'App\User';
}
if (!Helpers::modifyJWTAuthUser($auth_class))
return response()->json([
'status' => 0,
'error' => 'provider not found!'
]);
return $next($request); }
Defined a function with name modifyJWTAuthUser in Helpers and here is its inner:
public static function modifyJWTAuthUser($user_class){
if (!$user_class ||
(
$user_class != 'App\User' &&
$user_class != 'App\Admin' &&
$user_class != 'App\ServiceProvider'
))
return false;
try {
Config::set('jwt.user', $user_class);
Config::set('auth.providers.users.model', $user_class);
app()->make('tymon.jwt.provider.user');
return true;
} catch (\Exception $e) {
return false;
} }
Introduced another $routeMiddleware like below in Kernel.php:
...
'modify.jwt.auth.user' => ChangeJWTAuthUser::class,
and the last step, Adding 'modify.jwt.auth.user' middleware to the routes that you want.
But even with this steps, You must have encountered a new issue. It was about getting the auth token by credentials in login and getting auth user from the token. (It seems that changing config value not effect on JWTAuth::attempt($credentials) and $this->auth->authenticate($token))
To solve the getting auth user from the token issue:
Create a new middleware CustomGetUserFromTokenwhich extends of Tymon'sjwt.authmiddleware, I meanGetUserFromTokenand in line 35, and **replace**$user = $this->auth->authenticate($token);with$user = JWTAuth::toUser($token);`
And to solve getting the auth token by credentials in login issue:
At first, Find the auth user and after that, check the user existence and valid the password with Hash::check() method, if these conditions return true, Generate a token from the user. Here is login code:
$admin = Admin::where('email', $request->email)->first();
if (!$admin || !Hash::check($request->get('password'), $admin->password)) {
return response()->json([
'success' => '0',
'error' => 'invalid_credentials'
], 401);
}
I'm not sure about this way but I think it's true until finding a correct way to do!
Conclusion:
Having multi JWT auth ability in Laravel perhaps have many other ways to do but I did like this and shared it to be helpful.
I think the only important point of this issue was app()->make('tymon.jwt.provider.user');, the ability to remake user provider after config values change.
Any other solutions will be appreciated.
You should use just one model (actually table) for authentication. When you save user and admin you can handle it. But when a user has request with jwt token, you cann't know which model will return (Admin or User)?
Use only User model for authentication and Admin model extends from User.
Redesign database like this:
users table : id, email, password, is_admin
user_details table : id, user_id, first_name, last_name, city_abbr, phone
admin_details table: id, user_id, name, phone
Put this your Admin Model for overriding all queries:
protected $table = "users";
public function newQuery()
{
return parent::newQuery()
->where("is_admin", true);
}
Laravel 5.5
public function register(Request $request) {
request()->validate([
'email' => 'required:email'
'password' => 'required|min:6'
]);
return response()->json(["message" => "Hello World"]);
}
If validator is fails, not giving error messages. Redirecting main page.
If the code you're using redirects you to the previous page when validation fails, it means that you didn't tell the server what kind of response you want to receive.
Set a proper header to get JSON. It will make the validator send JSON in response. For example:
$.ajax({
headers: {
Accept : "application/json"
},
...
});
Then this code will work as expected:
public function register(Request $request)
{
$request->validate([
'email' => 'required:email'
'password' => 'required|min:6'
]);
return response()->json(["message" => "Hello World"]);
}
I had the same problem when testing my rest api in Postman application.
if we don't want to modify our current code of laravel redirect repose, we have to put Accept:-application/json and ContentType:-application/json
For modifying code in controller class file, i did it like this and got the json response instead of redirecting to home page.
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
} else {
// do something
}
}
before it looks like below codes it was redirecting to home page
This is validator function
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',
]);
}
public function register(Request $request)
{
// Here the request is validated. The validator method is located
// inside the RegisterController, and makes sure the name, email
// password and password_confirmation fields are required.
$this->validator($request->all())->validate();
// A Registered event is created and will trigger any relevant
// observers, such as sending a confirmation email or any
// code that needs to be run as soon as the user is created.
event(new Registered($user = $this->create($request->all())));
// After the user is created, he's logged in.
$this->guard()->login($user);
// And finally this is the hook that we want. If there is no
// registered() method or it returns null, redirect him to
// some other URL. In our case, we just need to implement
// that method to return the correct response.
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
You can do this like this :
$validator = Validator::make($request->all(), [
'email' => 'required|email', //use pipe here to apply multiple validations rules and add a ','
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()]);
}
return response()->json(["message" => "Hello World"]);
The validation is working well, but, $request->validate() will redirect you to the previous page. I recommend you to manually create your validations:
Manually Creating Validations.
You could do something like this:
use Illuminate\Http\Request;
use Validator;
class YourClass extends Controller{
public function yourFunction(Request $request) {
$validator = Validator::make($request->all(),[
'field_1' => 'rule1|rule2',
'field_2' => 'rule1|rule2'
]);
if ($validator->fails()) {
return response()->json($validator->errors());
} else {
/*Something else*/
}
}
}
try this, hope this code can help you
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:6'
]);
I have a small question. I create simple API using Laravel. When I use validation and if it fails, I got a common message:
{
"result": false,
"message": "The given data failed to pass validation.",
"details": []
}
But how can I get details about which field fails and why like that:
{
"result":false,
"message":"The given data failed to pass validation.",
"details":{
"email":[
"The email field is required."
],
"password":[
"The password must be at least 3 characters."
]
}
}
My code in controller looks like this:
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:3',
]);
return $validator;
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'role_id' => 2
]);
}
It is better to handle the validator within the same process, like this:
public function register(Request $request){
$validator = Validator::make($request->all(),[
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
if($validator->fails()){
return response()->json([
"error" => 'validation_error',
"message" => $validator->errors(),
], 422);
}
$request->merge(['password' => Hash::make($request->password)]);
try{
$user = User::create($request->all());
return response()->json(['status','registered successfully'],200);
}
catch(Exception $e){
return response()->json([
"error" => "could_not_register",
"message" => "Unable to register user"
], 400);
}
}
You should make sure you're sending the request with the Accept: application/json header.
Without that - Laravel won't detect that it's an API request,
If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.
check the documentation
I used validate in my project:
1.I created app/http/requests/CreateUserRequestForm.php
public function rules()
{
return [
"name" => 'required',
"address" => 'required',
"phnumber" => 'required|numeric',
];
}
public function messages()
{
return [
'name.required' => 'Please Enter Name',
'addresss.required' => 'Please Enter Address',
'phnumber.required' => 'Please Enter PhNumber'
];
}
call the RequestForm in controller
use App\Http\Requests\CreateUserRequestForm;
public function createUser(CreateUserRequestForm $request)
{
// create
$user= UserModel::create([
'name' => $request->input('name'),
'address' => $request->input('address'),
'phnumber' => $request->input('phnumber')
]);
return response()->json(['User' => $user]);
}
Try this i didn't try but it should be work for you.
You may use the withValidator method. This method receives the fully
constructed validator, allowing you to call any of its methods before
the validation rules are actually evaluated.
take reference from here. laravel validation
/**
* Configure the validator instance.
*
* #param \Illuminate\Validation\Validator $validator
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('email', 'Please enter valid email id');
}
});
}
Try this:
public function create(){
// ------ Validate -----
$this->vallidate($request,[
'enter code here`name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:3'
]);
// ------ Create user -----
$user = User::create(['name' => $request->name']);
return response()->json([
'message' => "success",
'data' => $user``
]);
}
I have problem with my authentication in laravel 5, it works if I enter the wrong username and password and the error appear, but when success, it doesn't redirect to dashboard, and no errors appear.
here's my code 'AuthController':
public function login(LoginRequest $request){
$this->validate($request, [
'username' => 'required', 'password' => 'required',
]);
$remember = (Input::get('remember')) ? true : false;
if($this->auth->validate(['username' => Input::get('username'),'password' => Input::get('password'), 'active' => 1], $remember)){
return redirect('/dashboard');
}else{
return redirect('/login')->withErrors([
'failed' => 'Username or Password is invalid. Please try again.',
]);
}
}
class AuthController extends Controller
{
protected $redirectTo = '/';
protected $loginPath = '/login';
}
Much easier....