I am quite new to Laravel and I am making a small test application.
But now I am stuck on the following issue.
After a user logged in to the appilcation I want him/her to see a form where he/her have to fill in more information about him/her self before they can continue.
My problem is that I dont know where to put the code for this, I tried placing it in the Controller.php but it does not seem to work (It does sort of only on the main page but not on the profile page), I also tried to put it in the AppServiceProvider.php as my main menu always recives some data from the database but that also didnt seem to work.
Does any one have an idea where to place the following code ?
if (\Request::path() !== 'info' && (\Auth::User()->firstname === NULL || \Auth::User()->lastname === NULL)
{
return \Redirect::to('info');
}
The info page will be the page where the user will see the additional information form.
I think you should create a middleware for that.
First run php artisan make:middleware AccountInfoMiddleware to create the needed file.
Then open app/Http/Middleware/AccountInfoMiddleware.php and add your code to the handle() method:
public function handle($request, Closure $next)
{
$user = \Auth::user();
if($request->path() !== 'info' && $user && ($user->firstname === NULL || $user->lastname === NULL)
{
return redirect('info');
}
return $next($request);
}
After that you have different ways to use your middleware:
Add App\Http\Middleware\AccountInfoMiddleware to the $middleware array in app/Http/Kernel.php. This means the middleware will run for every request.
Add the same thing with a name to the $routeMiddleware in Kernel.php and use it for specific routes or enable it from the controller. For more information, visit the Laravel documentation
If you means you want to redirect user if the user dont have firstname and lastname, You can use middleware to solve your problem. Place your code in middleware file.
Create Middleware file
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Routing\Middleware;
class UserMiddleware implements Middleware {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (\Request::path() !== 'info' && (\Auth::User()->firstname === NULL || \Auth::User()->lastname === NULL)
{
return \Redirect::to('info');
}
return $next($request);
}
}
Register middleware for route in app/Http/Kernel.php
/**
* The application's global HTTP middleware stack.
*
* #var array
*/
/**
* The application's route middleware.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'user' => 'App\Http\Middleware\UserMiddleware' // Register this new middleware
];
Use that middleware for the route you want
Route::get('/', [
'middleware' => 'user',
'uses' => 'ExampleController#index'
]);
After you add the middleware for routes, every request, handle() function inside middleware file will be run to check if the user have firstname or lastname. If the user dont have, it will redirect to info and if the user have all the info, it will process next request.
Related
I use Laravel 5.2, and I want to know how to force a user to log out by id. I'm building an admin panel with the option to deactivate a specific user that is currently logged in to the web application. Laravel gives you this option for a current user.
Auth::logout()
But I don't want to log out the current user, as I am an authenticated user. So I need to force log out of a specific user by its id. Just like when we log in a user with a specific id.
Auth::loginUsingId($id);
Is there something like the following?
Auth::logoutUsingId($id);
Currently, there's no straightforward way to do this; As the StatefulGuard contract and its SessionGuard implementation don't offer a logoutUsingId() as they do for login.
You need to add a new field to your users table and set it to true when you want a specific user to be logged out. Then use a middleware to check if the current user needs a force logout.
Here's a quick implementation.
1. Add a new field
Let's add a new field to users table migration class:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
// ...
$table->boolean('logout')->default(false);
// other fields...
});
}
// ...
}
Make sure you run php artisan migrate:refresh [--seed] after changing the migration.
2. Force logout middleware
Let's create a new middleware:
php artisan make:middleware LogoutUsers
Here's the logic to check if a user needs to be kicked out:
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
class LogoutUsers
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = Auth::user();
// You might want to create a method on your model to
// prevent direct access to the `logout` property. Something
// like `markedForLogout()` maybe.
if (! empty($user->logout)) {
// Not for the next time!
// Maybe a `unmarkForLogout()` method is appropriate here.
$user->logout = false;
$user->save();
// Log her out
Auth::logout();
return redirect()->route('login');
}
return $next($request);
}
}
3. Register the middleware in HTTP kernel
Open up the app/Http/Kernel.php and add your middleware's FQN:
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LogoutUsers::class, // <= Here
],
'api' => [
'throttle:60,1',
'bindings',
],
];
It's untested code, but it should give you the idea. It'd be a good practice to add a couple of API methods to your User model to accompany with this functionality:
markedForLogout() : Checks user's logout flag.
markForLogout() : Sets user's logout flag to true.
unmarkForLogout() : Sets user's logout flag to false.
Then on the administration side (I suppose it's your case), you just need to call markForLogout() on the specific user model to kick him out on the next request. Or you can utilize the query builder to set the flag, if the model object is not available:
User::where('id', $userId)
->update(['logout' => true]);
It can be a markForLogoutById($id) method.
Related discussions
[Proposal] Log out users by ID
Multiple statements when logged users are deleted
Use the setUser to find a soluion
get current user
$user = Auth::user();
logout user you want to, by id
$userToLogout = User::find(5);
Auth::setUser($userToLogout);
Auth::logout();
set again current user
Auth::setUser($user);
I suggest you to override App\Http\Middleware\Authenticate handle function with custom check
if ($this->auth->user()->deleted_at) {
$this->auth->logout();
return redirect()->route('login');
}
The setup and below code I use it to logout a user and also lock his account(optional)using checklist, I did it through Ajax Request:
-In config/session.php, change to:
'driver' => env('SESSION_DRIVER', 'database'),
-run php artisan session:table
-run php artisan migrate //a session table is created.
-in .env file: change SESSION_DRIVER=file to SESSION_DRIVER=database
Now the coding part:
-In controller:
function ajaxCheckList(Request $request)
{
// \Log::info($request);
$user = \App\Models\User::findOrFail($request['user_id']);
$locked = 0;
if ($user->locked == 1) {
$user->locked = 0;
$locked = 0;
} else {
$user->locked = 1;
$locked = 1;
DB::table('sessions')
->where('user_id', $request['user_id'])
->delete();
}
$user->update(['locked' => $locked]);
}
I'm still very noobish with Larvel, so i can't figure out how to do this:
I'm stuck at the login system. I'm trying to understand how i can prevent inactive users to use the application.
Edit
With inactive users i mean those users which has the record field 'active' on 0. So i need to implement a check which verifies the mail and the password and also the active field
I've made the tables and the auth system, and as written in the documentation, I've placed this method inside my AuthController:
public function authenticate()
{
dd('hi');
}
Well it completely gets ignored. It seems like it never gets triggered. Am I missing something?
My controller looks like the original Laravel's 5.2 AuthController except for the method before. No other changes has been made, since according to the documentation no other changes are mentioned...
My tests:
I've also searched for a method called authenticate. But no methods where found (using php storm). So my question is:
If it's a trait, isn't supposed to have that method declared? So i can override it just by declaring a method with the same name?
Important files:
Routes.php
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
Route::get('/', 'HomeController#index');
Route::auth();
Route::get('/home', 'HomeController#index');
SOLVED
If you type php artisan route:list you see when login by post method, user post their info account to login action in LoginController. So, I've solved the problem by override login method in Auth\LoginController as below:
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
$credentials['active'] = 1;
if (Auth::attempt($credentials)) {
// Authentication passed...
return redirect()->intended('/');
}
}
Firstly you need to remove the Auth routes but before that run php artisan route:list in your terminal. Copy the auth related routes and stick them in your routes file.
Change the controller in each of the routes to point to your own AuthController in your application.
Next, include the following 2 traits into your controller (top of the page) plus 2 classes;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
and then include the traits in the top of your class;
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
as it stands, the auth functionality should still work as normal.
Then, alter the login method to accept an active flag. Add the following to your new controller;
/**
* [login description]
* #param Request $request [description]
* #return [type] [description]
*/
public function login( Request $request, $guard )
{
$this->validateLogin( $request );
$throttles = $this->isUsingThrottlesLoginsTrait();
if ( $throttles && $lockedOut = $this->hasTooManyLoginAttempts( $request ) ) {
$this->fireLockoutEvent( $request );
return $this->sendLockoutResponse( $request );
}
$credentials = $this->getCredentials( $request );
if ( Auth::guard( $guard )->attempt( [ 'active' => 1 ] + $credentials, $request->has( 'remember' ) ) ) {
return $this->handleUserWasAuthenticated( $request, $throttles, $guard );
}
// check to see if they can login without the active flag
if( Auth::guard( $guard )->attempt( $credentials ) ) {
// log them back out and send them back
Auth::guard( $guard )->logout();
return redirect()->back()
->withInput( $request->only( $this->loginUsername(), 'remember' ) )
->withErrors([
'active' => 'Your account is currently not active',
]);
}
if ( $throttles && ! $lockedOut ) {
$this->incrementLoginAttempts( $request );
}
return $this->sendFailedLoginResponse( $request );
}
using the traits in your own controller should give you more flexibility in the future.
regards
To prevent guest users (not authenticated) to access certain routes, you should protect those routes in your routes.php:
// All the routes inside the group are accessible for logged in users only
Route::group(array('before' => 'auth'), function() {
Route::get('myroute', 'Controller#action');
});
// For routes accessible for everybody
Route::get('login', function(){
return View::make('login');
});
Solved
I've managed to do this by myself. Not sure that is the proper way, but anyway it works.
I've decided to create a middleware that after the successful login, checks that the user 'active' field is 1. If not it redirects to a specific page.
public function handle($request, Closure $next)
{
// If the user is not logged in
if(!Auth::user()){
return redirect('login');
}
// If the user is inactive
if(!Auth::user()->active){
return redirect('inactive');
}
return $next($request);
}
I'm actually implementing 2-factor auth into the project. What I did was
Auth::user()->google2fa_passed = 1;
In fact it doesn't really store, when navigate to another page, the value is missing.
I also don't want to keep in another session, because when user logout (or users remove the session cookie from their browser), then will show a login page, and go through the 2 factor auth again.
Any idea how to save 1 more attribute to user session?
When you use Auth::user() it give you the Eloquent model of the authenticate user.
If you want to store data in session you need to use the Session facade or session() helper.
You can find more information about session in the documentation.
PS: Old version of the documentation is better (http://laravel.com/docs/5.0/session).
Eventually, I use session to store.
After key in the 6-digits code, store a flag into session
\Session::put('totp_passed', 1);
In app/Http/Middleware/Authenticate.php, remove the 2FA session if session expired
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
// remove the 2-factor auth if the user session expired
\Session::forget('totp_passed'); // <------- add this line
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->route('auth.login');
}
}
return $next($request);
}
Then create another middleware, e.g. app/Http/Middleware/TwoFactorAuth.php
namespace App\Http\Middleware;
use Closure;
class TwoFactorAuth
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!\Session::has('totp_passed')) {
return redirect()->route('auth.2fa');
}
return $next($request);
}
}
In app/Http/Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'2fa' => \App\Http\Middleware\TwoFactorAuth::class, // <------ add this line
];
How to use
Route::group(['middleware' => 'auth'], function () {
// must be login first only can access this page
Route::get('2fa', ['as' => 'auth.2fa', 'uses' => 'Auth\AuthController#get2FactorAuthentication']);
Route::post('2fa', ['uses' => 'Auth\AuthController#post2FactorAuthentication']);
// add 2-factor auth middleware
Route::group(['middleware' => '2fa'], function () {
// all routes that required login
});
});
I've started learning Laravel 5.1 and so far I'm liking it! But there is one thing I don't get yet..
In my previous project I had 2 specific controllers (eg: "normal", "extended") which , after a successfull login, were called based on the Users user_group from the database.
If "Foo.Bar" enters his valid credentials and has the group normal he is redirected to NormalControler. Since I wasn't using any framework I restricted access to the other group by setting a $_SESSION with the group and checking it. So if another group tried to access that controller he got redirected.
How would this be achievable in Laravel 5? So far I have a controller which is callable without an Authentication and one restricted by this code in routes.php :
// All routes in the group are protected, only authed user are allowed to access them
Route::group(array('before' => 'auth'), function() {
// TO-DO : Seperate Controller access
});
And the login looks like this :
public function performLogin()
{
$logindata = array(
'username' => Input::get('user_name'),
'password' => Input::get('user_pass')
);
if( Auth::attempt( $logindata ) ){
// return \Redirect::to( check group and access this controller based on it);
}
else {
// TO-DO : Redirect back and show error message
dd('Login failed!');
}
}
----- EDIT -----
I've run the artisan command and made this middleware as you suggested :
namespace App\Http\Middleware;
use Closure;
use Request;
class GroupPermissions
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $group)
{
// Check User Group Permissions
if( $request->user()->group === $group ){
// Continue the request
return $next($request);
}
// Redirect
return redirect('restricted');
}
}
and edited this line into Kernel.php into $routeMiddleware :
'group.perm' => \App\Http\Middleware\GroupPermissions::class
I think this is done right so far, correct me if I'm wrong! Could I then do something like this to restrict the controllers?
Route::group(array('before' => 'auth'), function() {
Route::group( ['middleware' => 'group.perm', 'group' => 'normal'], function(){
Route::get('/normal/index', 'DummyNormalController#index');
});
Route::group( ['middleware' => 'group.perm', 'group' => 'extended'], function(){
Route::get('/extended/index', 'DummyExtendedController#index');
});
});
Ok, here is what you might do. Once user is logged in, you would check his credentials, get his user_group and decide what controller he should be redirected to.
if( Auth::attempt( $logindata ) ){
$user = Auth::user();
if ($user->inGroup('normal')) {
return redirect()->route('normal_controllers_named_route');
}
return redirect()->route('extended_controllers_named_route');
}
return redirect()->back()->withFlashMessage('don\'t get me wrong');
This will handle right routing after logging in.
The next portion where you need to protect you routes from unwanted user groups may be achieved with middlewares.
do an artisan command php artisan make:middleware ShouldBeInGroup
go to app/http/Kernel.php and add your new middleware to the routeMiddleware array. Key of the item might be anything you like. Let's call in inGroup. So: 'inGroup' => 'App\Http\Middleware\ShouldBeInGroup'
Now, in your controller, in constructor, you are able to call this middleware
$this->middleware('inGroup:extended'); //we also passing the name of the group
at lastly, work on the our middleware. Open newly created ShouldBeInGroup class and edit the handle method.
public function handle($request, Closure $next, $groupName)
{
if (Auth::check() && Auth::user()->inGroup($groupName)) {
return $next($request);
}
return redirect('/');
}
And finally you should work on inGroup method, that should return true of false. I assume that you have user_group field your users table. Then in your User eloquent model add the method
public function inGroup($groupName) {
return $this->user_group == $groupName;
}
Edit
if you want to use this middleware in your routes, you can do the following
Route::group(array('before' => 'auth'), function() {
Route::get('/normal/index', ['middleware' => 'group.perm:normal', 'uses' =>'DummyNormalController#index']);
}
But generally it's better to put all your middlewares into your Controller's constructor
public function __construct(){
$this->middleware('group.perm:normal'); // you can also pass in second argument to limit the methods this middleware is applied to : ['only' => ['store', 'update']];
}
And also on this note, Laravel provides built in auth middleware that you can use
public function __construct(){
$this->middleware('auth');
$this->middleware('group.perm:normal');
}
so then your routes would become much cleaner, just:
Route::get('normal/index', 'DummyNormalController#index');
I think the best way to do that is using middlewares. See the doc here
You can easily create a middleware using the following artisan command:
php artisan make:middleware ExtendedMiddleware
If you can't or don't want to use artisan, you need to create a class in The App/Http/Middleware folder.
In this class you'll need the following method to handle the request. In the method you can check for the user group.
public function handle($request, Closure $next)
{
// check user group
if( user_group_ok )
return $next($request); // Continue the request
return redirect('restricted'); // Redidrect
}
You can then use this middleware in your route.php file:
Route::group(['middleware' => 'auth'], function()
{
// Logged-in user with the extended group
Route::group(['middleware' => 'extended'], function()
{
// Restricted routes here
});
// Normal routes here
});
You can create a Middleware called : PermissionFilter
In PermissionFilter, you check if requesting user is in the group or not.
I can't provide a demo for now, but if you want I can make a demo later.
L5 middleware: http://laravel.com/docs/5.1/middleware
I am new to Laravel but fell in love with the framework and decided to use it for my project.
I have a field active and by default I've set it to 0. In the Attempt() method, I've set $credentials['active'] = 1. When I logout and login again, this works fine.
But when I register a user, it automatically logs the user in without checking active field.
I assume you are using the AuthenticatesAndRegistersUsers trait in your controller.
The registration is carried by the postRegister() method in that trait, which calls the login() method after creating a new user.
You can override this method in your controller and call the login() method only when the active field is true. So, your postRegister() method will be something like:
public function postRegister(Request $request)
{
$validator = $this->registrar->validator($request->all());
if ($validator->fails())
{
$this->throwValidationException(
$request, $validator
);
}
$user = $this->registrar->create($request->all());
if ($request->get('active')) {
$this->auth->login($user);
}
return redirect($this->redirectPath());
}
In registersUsers.php replace the line:
Auth::guard($this->getGuard())->login($this->create($request->all()));
With the following:
$this->create($request->all());
This worked for me, I use Laravel 5.2
I would avoid adding the active field to the credentials - this is an authorisation issue rather than an authentication one.
For this, I'd use a middleware to check if a logged-in user is active or inactive.
In 5.3, the middleware would look like this:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfInactive
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ( $user = Auth::guard('web')->user() ) {
if ( ! $user->active )
return redirect(route('account_inactive'));
}
return $next($request);
}
}
This middleware then has to be registered inside Kernel.php:
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'inactive' => \App\Http\Middleware\RedirectIfInactive::class,
];
And then finally we protect all our routes with it:
Route::get('inactive', ['as' => 'account_inactive', function () {
return view('inactive');
}]);
Route::group(['prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'inactive'], function () {
Route::get('/', ['as' => 'admin.home', 'uses' => 'AdminController#index']);
});
The advantage of this approach of course is that we can display a more relevant error message than the general 'These credentials do not match our records' that people with bad auth details get. So the user would know it's not their fault they can't log in.
Anyway, with the approach in the accepted answer, ensure you have done the same for when the user successfully resets their password as they are also auto-logged in then.
For future reference, you could just override the registered method and inside it, destroy all sessions by using Auth::logout. The user will by default be redirected to Login page