I want to register on my site with an invitation code, so I added in my controller If statement for checking the activation code
protected function create (array $data)
{
if ($data['activation_code'] === 'mypassword123') {
return User::create([
'name' => $data['name'],
'lastname' => $data['lastname'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
return redirect('/register')->with('message', 'De activatiecode is onjuist');
}
}
And when I submit the registration I get this error
Argument 1 passed to Illuminate\Auth\SessionGuard::login() must be an instance of Illuminate\Contracts\Auth\Authenticatable, instance of Illuminate\Http\RedirectResponse given, called in C:\xampp\htdocs\Suppliers\vendor\laravel\framework\src\Illuminate\Foundation\Auth\RegistersUsers.php on line 35
So I don't know where the problem is, please help
Like the comment said protected function create (array $data) is not responsible for redirecting to a different page and the logic, I think, should not be there either.
you can either overwrite the register method from trait RegistersUsers or create a middleware.
overriding register method, in RegisterController add the logic here.
/**
* Handle a registration request for the application.
*
* #return \Illuminate\Http\Response
*/
public function register(Request $request)
{
if ('mypassword123' !== $request->input('activation_code')) {
return redirect('/register')->with('message', 'De activatiecode is onjuist');
}
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
Middleware
<?php
namespace App\Http\Middleware;
use Closure;
class CheckCode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
*
* #return mixed
*/
public function handle($request, Closure $next)
{
if ('mypassword123' !== $request->input('activation_code')) {
return redirect('/register')->with('message', 'De activatiecode is onjuist');
}
return $next($request);
}
}
And then add it when you need it.
use App\Http\Middleware\CheckAge;
Route::get('someroutes', function () {
//some code
})->middleware(CheckCode::class);
Hope this helps.
Related
I am trying to change hashing in the laravel.
So I made custom SHA256 with salt in the RegisterController.
Register completed but how to change in the login?
protected function create(array $data)
{
$salt = Str::random(8);
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => '$SHA$' . $salt . '$' . hash('sha256', hash('sha256', $data['password']) . $salt),
]);
}
This is code of LoginController. $this->guard()->attempt($this->credentials($request)) this goes to something and hash then get token.
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Exceptions\VerifyEmailException;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* Attempt to log the user into the application.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function attemptLogin(Request $request)
{
$token = $this->guard()->attempt($this->credentials($request));
if (! $token) {
return false;
}
$user = $this->guard()->user();
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
return false;
}
$this->guard()->setToken($token);
return true;
}
/**
* Send the response after the user was authenticated.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\JsonResponse
*/
protected function sendLoginResponse(Request $request)
{
$this->clearLoginAttempts($request);
$user = $this->guard()->user();
$token = (string) $this->guard()->getToken();
$expiration = $this->guard()->getPayload()->get('exp');
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => $expiration - time(),
]);
}
/**
* Get the failed login response instance.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\JsonResponse
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$user = $this->guard()->user();
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
throw VerifyEmailException::forUser($user);
}
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
]);
}
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
}
}
Here is (what I believe is) the correct way to add hashing functions:
Step 1: Create your hasher by implementing the Hasher contract:
namespace App;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class Sha256Hasher implements HasherContract {
public function make($value, array $options = []) {
$salt = Str::random(8);
return '$SHA$' . $salt . '$' . hash('sha256', hash('sha256', $data['password']) . $salt),
}
public function info($value) {
// Implement something that works like https://www.php.net/manual/en/function.password-get-info.php
}
public function check($value, $hashedValue, array $options = [])) {
// Verify the hash here e.g.
return $this->make($value, $options) === $hashedValue;
// But more secure than this
}
public function needsRehash($hashedValue, array $options = []) {
return <a boolean whether the passwords needs rehashing>;
}
}
You can then extend the hashers with this hasher. In a service provider add:
Hash::extend('sha256', function () {
return new Sha256Hasher();
});
Then (finally) change your default hashing driver in your config/hashing.php:
'driver' => 'sha256',
This should switch your hashing to use your new driver and should not need any changes to views or models.
First, create this function where you can reuse it:
protected function hash($string){
return hash('sha256', $string . config('app.encryption_key'));
}
On user creation you have to call the function to hash the password:
protected function create(array $data){
return User::create([
'name' => $data['name'],
'password' => $this->hash($data['password'])
]);
}
On login, you would have to call hash function on password again:
protected function login(Request $request){
$user = User::where([
'email' => $request->request('email'),
'password' => $this->hash($request->input('password'))
])->first();
Auth::login($user);
$token = $user->createToken('MyApp')->accessToken;
return response()->json(compact('token', 'user'));
}
I think that is the best approach to consider.
I'm making an app that uses jwt as authentication system ,
when I try to update my Category model the policy always returns 403 unauthorized,
I'm using apiResource to crud my model.
my code
in api.php:
Route::apiResource('category', CategoryController::class);
in CategoryController.php:
public function update(Request $request, $id)
{
// print_r($request->all());
$validator = Validator::make(
$request->all(),
[
'name' => 'required|min:2|unique:categories,name,' . $request->id,
'description' => 'required|min:1',
],
[
"name.unique" => "اسم الصنف مستخدم مسبقا",
"name.required" => "اسم الصنف مطلوب",
"name.min" => "اسم الصنف يجب أن يحتوي على حرفين على الأقل",
"description.required" => "وصف الصنف مطلوب",
]
);
if ($validator->fails()) {
return response()->json(['errors' => $validator->messages(), 'status' => 422], 200);
}
$category = Category::find($id);
$category->name = $request->name;
$category->description = $request->description;
$category->save();
return response()->json([
"message" => "تم تحديث الصنف",
"status" => 200
], 200);
}
in CategoryPolicy.php:
public function update(User $user, Category $category)
{
return $category->user_id === $user->id;
}
It seems like the request is not even reaching the update method in CategoryPolicy.php
because even if the method always returning true it's not working :
public function update(User $user, Category $category)
{
return true;
}
any way the viewAny method is working as expected.
I'm using axios to fetch and update data and I'm sending the request with the bearer token and every thing is working ok except the issue above.
In CategoryController.php, instead of injecting $id:
public function update(Request $request, $id)
Try injecting the type-hinted model instance:
public function update(Request $request, Category $category)
And remove the find() command:
//$category = Category::find($id);
When generating new controllers, you can also use this artisan command to include type-hinted models in the function arguments.
php artisan make:controller CategoryController --api --model=Category
It's hard to see what is going wrong because it can also be the middleware and JWT token. What you could do is in your update method check if the user is logged in, add the following as the first line in the method. If false please check your JWT implementation
dd(auth()->check());
I would also suggest clean up your controller:
class CategoryController
{
/**
* CateogryController constructor.
*/
public function __construct()
{
$this->authorizeResource(Category::class); // if your are using CRUD, validate like this
}
/**
* Update specific resource.
*
* #param Category $category
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function update(Category $category, CategoryRequest $request): JsonResponse
{
// notice the model route binding.
$this->authorize('update', $category); // If you only have update method, but remove the __construct.
$category->update([
'name' => $request->get('name'),
'description' => $request->get('description')
]);
return response()->json(['message' => 'تم تحديث الصنف']); // take the 200 from the headers, not add it in as text.
}
}
Your request looks similar to this:
class CategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true; // you could consider to validate the user->category relation. I like it more separated and put it in a separated policy.
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|min:2|unique:categories,name',
'description' => 'required|min:1',
];
}
/**
* #return string[]
*/
public function messages()
{
return [
"name.unique" => "اسم الصنف مستخدم مسبقا",
"name.required" => "اسم الصنف مطلوب",
"name.min" => "اسم الصنف يجب أن يحتوي على حرفين على الأقل",
"description.required" => "وصف الصنف مطلوب",
];
}
}
And your policy like:
class CategoryPolicy
{
use HandlesAuthorization;
/**
* Determine if the user can update category resource.
*
* #param User $user
* #param Category $category
* #return bool
*/
public function update(User $user, Category $category): bool
{
return $user->categories()->where('id', $category->id)->exists(); // or somthing like this.
}
}
I am using Request validation to validate the user's input.
This is UpdateUser:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Gate::allows('update-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$user_id = Arr::get($this->request->get('user'), 'id');
return [
'user.firstname' => 'required|string|max:255',
'user.lastname' => 'required|string|max:255',
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
'user.password' => 'sometimes|nullable|string|min:4|confirmed',
];
}
}
As you can see, there is some update-specific stuff happening:
The authorize() method checks whether the user is allowed to update-user and inside the rules I am excluding the row of the current user from being unique:
'user.email' => "required|string|email|max:255|unique:users,email,{$user_id}",
As I would like to merge UpdateUser and StoreUser, what would be the most efficient and readable way to determine, whether I am updating or saving?
This is my current approach:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class UpdateUser extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
if($this->isUpdating())
{
return Gate::allows('update-user');
}
return Gate::allows('create-user');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
if($this->isUpdating()){
$user_id = Arr::get($this->request->get('user'), 'id');
return [
...
];
}
return [];
}
/**
* #return bool
*/
protected function isUpdating(){
return $this->isMethod('put') || $this->isMethod('patch');
}
}
I am wondering if I may extend the FormRequest class and provide isUpdating() by default.
Your update and store method are not the same request type, you have PUT and PATCH method on your request instance, so you can check the request type as like :
switch ($request->method()) {
case 'PATCH':
// do anything in 'patch request';
break;
case 'PUT':
// do anything in 'put request';
break;
default:
// invalid request
break;
}
I learnt about a new approach to validation some time ago using separate validator class and I kinda like it a lot. Let me show you
Create a directory Validators and a class inside UserValidator
class UserValidator
{
public function rules(User $user)
{
return [
'user.firstname' => [
'required',
'string',
'max:255',
],
'user.lastname' => [
'required',
'string',
'max:255',
],
'user.email' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'email',
'max:255',
Rule::unique('users', 'email')->ignore($user->exists ? $user->id : null)
],
'user.password' => [
$user->exists ? 'sometimes' : null,
'required',
'string',
'min:8'
],
];
}
public function validate(array $data, User $user)
{
return validator($data, $this->rules($user))
//->after(function ($validator) use ($data, $user) {
// Custom validation here if need be
//})
->validate();
}
}
Then authorization can be done in Controller
class UserController
{
use AuthorizesRequests;
/**
* #param Request $request
*/
public function store(Request $request)
{
$this->authorize('create_user', User::class);
$data = (new UserValidator())->validate(
$request->all(),
$user = new User()
);
$user->fill($data)->save();
}
/**
* #param Request $request
* #param \App\user $user
*/
public function update(Request $request, User $user)
{
$this->authorize('update_user', $user);
$data = (new UserValidator())->validate(
$request->all(),
$user
);
$user->fill($data)->save();
}
}
This was proposed and explained by (twitter handle) #themsaid
I am created a Laravel 5.2 application; I need to limit failure login attempts.I am created a AuthController with following code; But not working logging attempt lock.
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use Validator;
use Auth;
use URL;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers;
protected $maxLoginAttempts=5;
protected $lockoutTime=300;
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
$this->loginPath = URL::route('login');
$this->redirectTo = URL::route('dashboard'); //url after login
$this->redirectAfterLogout = URL::route('home');
}
public function index()
{
return 'Login Page';
}
/**
* Handle a login request to the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postLogin(Request $request)
{
$this->validate($request, [
'username' => 'required', 'password' => 'required',
]);
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $this->hasTooManyLoginAttempts($request)) {
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::attempt($credentials, $request->has('remember'))) {
return redirect()->intended($this->redirectPath());
}
if ($throttles) {
$this->incrementLoginAttempts($request);
}
return redirect($this->loginPath)
->withInput($request->only('username', 'remember'))
->withErrors([
'username' => $this->getFailedLoginMessage(),
]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function getCredentials(Request $request)
{
return $request->only('username', 'password');
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'username' => $data['username'],
'password' => bcrypt($data['password']),
]);
}
}
After many failure login their is no error message displayed. I am added some line to display error in login.blade.php file
Assuming you have implemented the make:auth artisan command of laravel.
Inside of the loginController, change the properties:
protected $maxLoginAttempts=5; to protected $maxAttempts = 5;
and
protected $lockoutTime=300; to protected $decayMinutes = 5; //in minutes
you need to use ThrottlesLogins trait in your controller
....
use AuthenticatesAndRegistersUsers, ThrottlesLogins ;
...
take a look here https://github.com/GrahamCampbell/Laravel-Throttle
and here https://mattstauffer.co/blog/login-throttling-in-laravel-5.1
Second link is for L5.1, but I think shouldnt be different for L5.2
Hope it helps!
Have a nice day.
Just overriding the following 2 functions maxAttempts and decayMinutes will be good to go. This 2 functions belong to Illuminate\Foundation\Auth\ThrottlesLogins.php file. I have tested on Laravel 5.6 version and working fine.
public function maxAttempts()
{
//Lock on 4th Failed Login Attempt
return 3;
}
public function decayMinutes()
{
//Lock for 2 minutes
return 2;
}
since a few time I started programming in Laravel for a schoolproject. Now I tried to implement a login system. My database for userinformation is running on my homestead virtual machine, running with Postgresql. I already migrated the tables, so they exist and the database is running. Problem is: When I fill in the registrationform and send it, I get an error:
FatalErrorException in RegistersUsers.php line 32: Call to a member function fails() on null
You can see the code of RegistersUsers.php underneath:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
use RedirectsUsers;
/**
* Show the application registration form.
*
* #return \Illuminate\Http\Response
*/
public function getRegister()
{
return view('auth.register');
}
/**
* Handle a registration request for the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
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());
}
}
When I try to log in it gives the warning the log in credentials doesn't exist in the database (because I haven't registered yet), so it seems that the database connection works properly.
Does someone know a solution for this?
----UPDATE----
Underneath I added my AuthController is included. You can also see my project on my github, so you can look into my other files if needed: https://github.com/RobbieBakker/LaravelProject56
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
private $redirectTo = '/';
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
I eventually solved it just by commenting the function which called this error. I'm not sure if it's supposed to be solved like that, but my login system seems to work fine now.
Thanks for your support!
You forgot return validator instance from your validator function ))
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
return $validator;
}
I had same error )
I think you need to "make" the validator.
try changing it to this..
$validator = $this->validator->make($request->all(), $rules);
see if that works...