How can I properly authenticate a user as an admin in Laravel? - php

I am attempting to introduce "ghosting" into my application - wherein I can access our app from the POV of a user.
Currently using the loginUsingID function to achieve this, with a protected route only accessible by admins. However, I would also like to display to the admin that they are ghosting a user by displaying a bar across the top of our app.
I was thinking of adding a property to the user is_being_ghosted - setting it as false on logout, false on login, and true on ghostLogin.
But I realize there is a small chance an admin attempts to ghost a user, and it sets that property, and while they are investigating things within the account, the user themselves refreshes their page (they were already authenticated so do not need to login again). In that case they would see this "admin bar" across the top, which clearly I wouldn't want to happen.
Is there an efficient way to achieve what I'm trying to do here? Am I going about this the wrong way?

As jszobody has mentioned. You could rather manage the state inside the session. You secure the /ghost route and then if the original-user-id session is set you display your bar and an unghost link.
public function ghost(Request $request, $id)
{
$request->session()->put('original-user-id', Auth::user()->id);
Auth::loginUsingId($id);
return redirect('/');
}
public function unghost(Request $request)
{
if ($request->session()->has('original-user-id')) {
Auth::loginUsingId($request->session()->pull('original-user-id'));
}
return redirect('/');
}
Update:
The ghost endpoint basically accepts the id that you want to impersonate, typically found through an ajax search input or something similar. Whatever suites your use case.

Related

Is it possible in LARAVEL to tag a user as logged in when getting his username from a custom URL?

I am currently doing a website wherein the login URLs are varying and displays the data according to the assigned projects to them.
For example, user A can only access www.example.com/projects/proj1. This is the homepage for user A and if he logs in he uses www.example.com/projects/proj1/login
While user B can only access www.example.com/projects/proj2. This is the homepage for user B and if he logs in he uses www.example.com/projects/proj2/login
Please note that proj1 and proj2 are varying depending on the database. So I have to check first that these projects are already registered in the database.
I am thinking of having a route like this.
For web.php
Route::get('/projects/{project_name}', 'PageHandler\CustomPageController#projects');
Route::get('/projects/{project_name}/login', 'PageHandler\CustomPageController#login');
Route::put('/projects/{project_name}/auth/{user}', 'PageHandler\TestUserPageController#auth');
Then my customepagecontroller.php looks like this
class CustomPageController extends Controller
{
public function projects(string $projectName)
{
if (auth()->user() == null)
return redirect('/projects'. '/' . $projectName . '/login');
}
public function login(string $projectName)
{
return view('login')->with('projectName', $projectName);
}
public function auth(Request $request, string $projectName)
{
$username = $request->username;
//How to set $username as logged in?
// rest of the code to show the home page after authentication
}
}
login.blade.php basically just looks like a form submitting username and password and calling auth of CustomPageController with a string parameter for the URL
So my question is how can I set $username as logged in already using the Auth of Laravel? Or should I create my custom Authentication Controllers?
Now, this is the only approach I have in mind for me to enable the logging in of users to varying URLs. Please let me know if you have better approach.
Thank you!
If you only want to limit the project the users can access, I do not see a need to use 2 different login URLs (please correct me if there is a reason why you want different URLs for that), instead, you simply find which project the user belongs to from the database.
For authentication, Laravel allows you to implement authentication in a very easy way, you can refer to the documentation. Using Laravel's authentication would be easier and safer than writing your own one, and even if the default functionalities it provides may not be exactly the same as those you would want to achieve, you can still add your own things, which is still a lot easier than implementing it from scratch.
As for setting a user as logged in with Laravel's authentication services, you can use Auth::login($user);. Here, $user must be an implementation of the Illuminate\Contracts\Auth\Authenticatable contract. You can refer to this part of the documentation for more details.

Laravel testing. Is possible to use multiple calls in same test?

I have this test:
public function test_user_can_access_the_application_page(
{
$user=[
'email'=>'user#user.com',
'password'=>'user1234',
];
$response=$this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
$response=$this->call('GET','/application/index');
$response->assertLocation('/application/index');
}
After I log in, it directs me to the dashboard ok until now, but if I want to access the other page after that, I cant. This error comes up.
Expected :'http://mock.test/application/index'
Actual :'http://mock.test'
Aren't multiple calls allowed in the same test, or is another way to access other pages after login?
(Note: It's not possible to use factories for the actingAs so I need to login).
If you can't use factories for actingAs, then you should try with cookie.
Look at the https://github.com/firebase/php-jwt library.
I guess you will need to call the function as an user, since you can only access it logged in. Laravel provides the actingAs() method for such cases.
https://laravel.com/docs/7.x/http-tests#session-and-authentication
You can create a random User who has the permission to log into your app or take a seeded one and call the function acting as the chosen User.
$response=$this->actingAs($user)->call('GET','/application/index');
If you call it without actingAs(), your middleware will redirect you back to the login or home screen (what you defined in the LoginController ).
In my opinion this test case should have its own testing method. I recommend using a test method per route or per use case. It makes your tests clearly arranged and easy to understand.
If you want to be authenticated, the easiest way is to have PHPUnit simulate authentication using the actingAs() method.
This method makes the user authenticated, so you wouldn't want to test the login method with it. You should write your login tests separate from testing the other pages.
To answer your question, yes you can make multiple requests in the same test, but in this case linking the login test to the 'application/index' page likely does not make much sense.
public function test_the_user_can_login()
{
$user = [
'email'=>'user#user.com',
'password'=>'user1234',
];
$response = $this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
}
public function test_user_can_access_the_application_page()
{
$user = User::where($email, "user#user.com")->first();
$response = $this->actingAs($user)
->call('GET','/application/index');
$response->assertLocation('/application/index');
}
I experienced that in laravel 8 I use comment #test and involved second test!!! I mean if you use two function for test you must us #test that php artisan test, test both of them.

Problem with accessing global variable in Laravel application

I am trying to make a global variable in AppServiceProvider.php that I will need throught my whole application meaning in all blade files. This variable is $profile which gets the profile data from user and displays them in blades. I made it so when I am on my profile it shows authenticated user which is me and it is fine (in url is like this profile/Authuser), that Authuser is username from database. Problem is when I go to some other profile then I get error undefined username (in url profile/Someuser). I need help on to get that username in AppServiceProvider.php. Problem is in that $username in service provider. I don't know how to pass it in there globally. Any help is appreciated. Here is my code.
AppServiceProvider.php
public function boot()
{
$profileId = $this->getIdFromUsername($username); // Here is problem, I don't know how to get that username
view()->composer('*', function ($view) {
$view->with('profile', Auth::id() ? UserProfile::profileDetails($profileId, Auth::user()->id) : []);
});
Builder::defaultStringLength(191); // Update defaultStringLength
}
public function getIdFromUsername($username)
{
if ($user = User::where('username', $username)->first()) {
return $user->id;
}
return abort(404);
}
web.php
Route::get('profile/{profile}', 'UserProfileController#showProfile')->name('profile.show');
I believe you are over complicating yourself.
If I understand your app. A user has a Profile correct?
Go to your User Model and create a relation between User and Profile
public function userProfile()
{
return $this->hasOne('App\UserProfile');
}
With that, the profile will follow the user, and you don't need to be passing it around.
If you want the Profile for the current User.
Auth::user()->userProfile();
If you want the profile of another user then
$owner = User::where('username', $username)->first();
$owner->userProfile();
Basically you can have access to the profile of your logged in user, or any other user easily by just finding the user you want.
Now, if you really wish to have a Model in every view, you are placing it in the wrong place. You see, Service Providers are intended to tie things up, not to get data. What you are probably thinking about is a View Composer that you do tie in with a Service Provider, but the actual data comes from the Composer itself. You can learn more about View Composer in the Docs. https://laravel.com/docs/7.x/views#view-composers
View Composers are just one way of doing it, a quick google search brought up this question which offers 3 additional alternatives to the view composer.
How to pass data to all views in Laravel 5?
Hope that helps.

Allow admin users to see what other user type can see/do?

I have a Laravel web application consist of 2 types of user:
customer
admin
Base on their user type , they can see, and perform different things.
Customer
When log-in as customer, my customer will see different dashboard.
Admin
When log-in as admin, I can see a list of users in a table
Example,
userA
userB
userC
more …
Goal:
I want to see what customer see when click on one of the user on the list.
I couldn’t come up the solution for that.
IMO
Will Auth::user()->type work for this scenario ?
The goal is to render the page as Auth:user()->type == ‘customer’, when the actual Auth::user()->type == ‘admin’. I'm not entirely sure if what I am trying to do is possible.
How would I do something like that in Laravel ?
You could try what I did in one of my projects - implementation is pretty simple, maybe you can make use of that as well.
There is additional action in our AuthController that allows a user to switch to other users and remembers current user ID in session:
public function switchUser($userId)
{
// disallow switched users from switching again
if (Session::get('previous_user')) App::abort(403);
$user = User::findOrFail($userId);
Session::set('previous_user', Auth::id());
Auth::login($user);
return redirect('some path');
}
Second part is customized logout function, that for switched users switches them back to their original user account instead of logging out:
public function getLogout()
{
if ($previousUser = Session::get('previous_user')) {
Session::remove('previous_user');
Auth::loginUsingId($previousUser);
return redirect('some path');
}
Auth::logout();
return redirect('some path');
}
With that logic you'll be able to switch to other users and back. You might need to add permission checking, so that only admins can do that etc., link the customers in the list to the switch URL, anyway the core of the functionality is there in the code above.

CakePHP redirect on login depending on username

I have a CakePHP 1.3 application that has a login system, which works well. It uses a DB with a users table, which existed before creating this app.
I'm using Auth in my AppController. The login function looks like
function login() {}
and it's located in the users_controller.
Everything works fine, as I said, but I have problems trying to add a new functionality. I would like to, during the login process, detect if a user has introduced a specific combination of login/password (let's say admin/adminpwd). If so, the login should be succesful AND he would be taken to an admin area (/admin/index). Otherwise, the login process should work as usual.
Once in this admin area (controlled by an admin_controller), this user should be able to perform some actions exclusive to him, no to the rest of users (even if they type on the browser /admin/action).
I've read about ACL, and probably it would help with this, but it seems too complicated for what I really need. Is there any simple way to do this? I guess I should modify the login function, but I don't really know how exactly, and if there's anything else I should change... any ideas?
Yeah, ACL is pretty complicated (and powerful). But in your case, I'd suggest create a 'group' field in users table to distinguish the role of the user. So you can have more admins later if you want. It's more flexible than hard-code a certain login credential in your users_controller.
There are several things you need to do to:
Tell the Auth component to transfer control to you after the user logins, so you can determine their group and redirect them accordingly.
Check if a user in a group is accessing some other group's action: If you don't, a regular user just need to be logged in, and they can type in admin url (if they know about it) and they can do everything an admin can. This check will probably be done in before_something_() in app_controller or tap into Auth somewhere.
I don't remember all the details, but you can get everything you need in the Cake Cookbook. Good luck!
Let's just see some code...
class UsersController extends AppController {
// we're moving the variable to AppController!
public function login() {
$usrInfo = $this->Auth->user();
if (isset($usrInfo) {
// this index name might not be right. I'm going off memory please check this!
if (in_array($usrInfo['username'], $this->adminUsers)) {
// do your code here for admin users.
// could be a redirect or just changing the layout used
} else {
// is a user that is logged in but not in our admin list
}
}
}
To test if the user is logged in you would need to do something like the following:
class AppController extends Controller {
protected $adminUsers = array('joe_blow_uname', 'jane_blow_uname');
public function beforeFilter() {
$routing = Configure::read('Routing.admin');
$usrInfo = $this->Auth->user();
if (isset($this->params[$routing]) && isset($usrInfo)) {
if (!in_array($usrInfo['username'], $this->adminUsers)) {
// do code here for non-admin users using /admin prefix
}
}
}
}
Let me know if this doesn't help.
Or worse breaks something...
Edit:
This is really not the best way to do this obviously. ACL or setting up some kind of group in your database would probably be better. BUT, it is a relatively quick-n-dirty way that, for a small site, should work fine.

Categories