How to use middleware for custom UserProvider in Laravel 5.5? - php

I'm new to Laravel (5.5) and want to create an application with a custom authentication mechanism:
If the user is not logged in, he/she should be redirected to /login.
I want to use the session-guard.
I have written and registered custom classes
LdapUser extends Illuminate\Auth\GenericUser and
LdapUserProvider implements Illuminate\Contracts\Auth\UserProvider. Actually both are dummy implementations at the moment that return generic objects. Later, I want to check, if the user exists in my external ldap-directory and if the credentials are valid. Registration, internal user management or password reset are not intended. I just want to use the user's name and some properties in my blade templates.
I modified config/app.php and config/auth.php, so that this provider can be used.
Now, I wonder how to proceed in order to actually use the authentication and access the LdapUser in my blade templates. I tried {{ Auth::user()->name }}, which led to this error message: "Trying to get property of non-object."
How to I tell my app to check, if a valid session exists and otherwise redirect to the login page?
How can I access the user's properties inside the blade templates?
This is my route:
Route::group(['middleware' => 'auth'], function () {
Route::get('/helloworld', function () {return 'Hello World!';});
});
Route::get('/login', 'LdapLoginController#showLoginForm')->name('showLoginForm');
/helloworld now redirects to /login where I see the login-form, which is going to be handled by the LdapLoginController:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LdapLoginController extends Controller
{
use AuthenticatesUsers;
}
I would now expect that the session guard uses the LdapUserProvider to check the credentials:
namespace App\Providers;
use App\LdapUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class LdapUserProvider implements UserProvider
{
public function retrieveById($id)
{
return $this->dummyUser();
}
public function retrieveByCredentials(array $credentials)
{
return $this->dummyUser();
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
return true;
}
public function retrieveByToken($identifier, $token)
{
return new \Exception('not implemented');
}
public function updateRememberToken(Authenticatable $user, $token)
{
return new \Exception('not implemented');
}
protected function dummyUser()
{
$attributes = array(
'id' => 123,
'username' => 'chuckles',
'password' => \Hash::make('SuperSecret'),
'name' => 'Dummy User',
);
return new LdapUser($attributes);
}
}
Unfortunately, after submitting the login form, I'm always redirected back to /login, no matter what I enter :-(
Any help is greatly appreciated!

Related

Is there a way to use two authentication middlewares in laravel?

I implemented passport authentication in Laravel and the basic auth.
I have UserController and inside it, I have the constructor methode:
public function __construct()
{
$this->middleware('auth.basic.once')->except(['index', 'show']);
$this->middleware('auth:api')->except(['index', 'show']);
}
The OnceBasic middleware:
public function handle($request, Closure $next)
{
if(Auth::guard('api')->check())
return $next($request);
else
return Auth::onceBasic() ?: $next($request);
}
In the OnceBasic middleware, I'm able to check if the user authenticated using the auth:api then I prevent the authentication from trying to use the onceBasic, So it worked correctly when using the access token. But it fails when trying to authenticate using the onceBasic(email, password) because the auth:api trying to authenticate too and it fails(trying to call the redirectTo() methods inside the default \App\Http\Middleware\Authenticate.php )
My question is there a way to use both of these middlewares, to only successfully authenticate one and prevent the other from working?
My approach to using the same controller for two guards required pointing two separate groups of routes to the controllers. I provided an example in this answer to a similar question, here is the example code again:
<?php
Route::middleware(['auth:admin_api'])->group(function () {
Route::prefix('admin')->group(function () {
Route::name('api.admin.')->group(function () {
////////////////////////////////////////////////////////////
/// PLACE ADMIN API ROUTES HERE ////////////////////////////
////////////////////////////////////////////////////////////
Route::apiResource('test','App\Http\Controllers\API\MyController');
////////////////////////////////////////////////////////////
});
});
});
Route::middleware(['auth:api'])->group(function () {
Route::name('api.')->group(function () {
////////////////////////////////////////////////////////////
/// PLACE PUBLIC API ROUTES HERE ///////////////////////////
////////////////////////////////////////////////////////////
Route::apiResource('test', 'App\Http\Controllers\API\MyController');
////////////////////////////////////////////////////////////
});
});
So when an admin user goes to admin/test, it uses the admin auth guard, and when a normal user goes to /test it uses the standard auth guard. Both of these use the same controller.
I then created a base controller for my app. Here is how I determined with guard is being used to access the route in the constructor:
<?php
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
class BaseController extends Controller
{
protected $user;
protected $isAdmin = false;
public function __construct()
{
if(Auth::guard('admin_api')->check()) {
$this->user = Auth::guard('admin_api')->user();
$this->isAdmin = true;
} elseif(Auth::guard('api')->check()) {
$this->user = Auth::guard('api')->user();
$this->isAdmin = false;
} else {
return response()->json([
'message' => 'Not Authorized',
], 401);
}
}

Having some trouble understanding Facades and Service Providers

I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php

laravel5.1 Manually Authenticating Users

In my app I use the default authentification to login my users to their dashboard.
Now I would like to create a backend for supporters and one for admins.
I use this code at the moment to login as admin:
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App;
use Artisan;
use Validator;
use Auth;
class AdminLoginController extends Controller
{
public function index()
{
return view('admin.login');
}
public function authenticate(Request $request)
{
if (Auth::attempt(['username' => $request->username, 'password' => $request->password, 'id' => 1]))
{
// Authentication passed...
dd("correct");
}
else
{
dd("login data incorrect!");
}
}
}
How can I use now the well known redirects after login.
In my AuthController I use for the users I use this code:
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/dashboard';
protected $redirectAfterLogout = 'auth/login';
protected $redirectPath = '/dashboard';
1.) How can I use this in my code above? I would like to redirect admins logged into the app like users to specified urls using this laravel feature.
2.) What's the best way, to check more "ids" in the if statement? Because there will be more than one admin.
first of all you are checking username, password along with a ID. So only user with Id = 1 is going to be authenticated by your function. You dont need to put an id check there.
Now for the redirect you can make a function which returns back the url based on the user type. Here is a very basic example`
public function getRedirectUrl(){
if(auth()->user()->isAdmin()){
return url('/admin-dashboard');
}
elseif(auth()->user()->isUser()){
return url('/user-dashboard')
}
}
Make sure you have isAdmin() and isUser() function defined in your User model like
public function isAdmin(){
return $this->user_type == UserType::ADMIN;
}
So in your authentication function just do return redirect($this->getRedirectUrl())

Laravel 4 - Hardcoded authentication

I want to create authentication mechanism without need for database where only one person (admin) who knows right username and password (which I would hardcode) would be able to login. I still want to use Auth::attempt(), Auth::check() and other functions.
I found out that I could create my own User driver, but it seems to me that there should be something simpler.
Maybe it is not very nice solution, but I want as simple as possible website.
It may only seem there should be something simpler, but in fact that's as simple as you can get if you want to extend the authentication system. All the methods you're using through the Auth facade (like attempt, check, etc.), are implemented within the Illuminate\Auth\Guard class. This class needs a UserProviderInterface implementation to be injected into the constructor in order to work. Which means that in order to use the Auth facade you either need to use the already implemented DatabaseUserProvider or EloquentUserProvider, or implement your own provider that handles the simple login you want.
Although the article you linked to may look lengthy, to achieve what you need you might get away with much less code in the provider than you might think. Here's what I think is what you need:
1. In your app/config/auth.php change the driver to simple and append the desired login credentials:
'driver' => 'simple',
'credentials' => array(
'email' => 'user#email.com',
'password' => 'yourpassword'
)
2. Create a file in your app directory called SimpleUserProvider.php that has this code:
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\GenericUser;
use Illuminate\Auth\UserProviderInterface;
class SimpleUserProvider implements UserProviderInterface {
protected $user;
public function __construct(array $credentials)
{
$this->user = new GenericUser(array_merge($credentials, array('id' => null)));
}
// If you only need to login via credentials the following 3 methods
// don't need to be implemented, they just need to be defined
public function retrieveById($identifier) { }
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(UserInterface $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
return $this->user;
}
public function validateCredentials(UserInterface $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->password;
}
}
3. Lastly you'll need to register the new provider with the authentication system. You can append this to the app/start/global.php file:
Auth::extend('simple', function($app)
{
return new SimpleUserProvider($app['config']['auth.credentials']);
});
This should give you a simple (no database) user authentication while still being able to use Laravel's facades.
The accepted answer did not work for me. Every time I logged in, the login was successful but when on the /home page, I was redirected to the login page again.
I found out that this was due to the user not being stored in the session as authenticated user. To fix this, I had to implement the getAuthIdentifier method in the User model class and also implement the retrieveById method .
I've also adjusted my solution to support multiple hard coded users (it presumes, that the email is unique, so we can also use it as id for the user):
1. In app/config/auth.php:
'providers' => [
'users' => [
'driver' => 'array',
],
],
'credentials' => [
'userA#email.com' => 'passA',
'userB#email.com' => 'passB',
]
2. The UserProvider:
use \Illuminate\Auth\GenericUser;
use \Illuminate\Contracts\Auth\UserProvider;
use \Illuminate\Contracts\Auth\Authenticatable;
class ArrayUserProvider implements UserProvider
{
private $credential_store;
public function __construct(array $credentials_array)
{
$this->credential_store = $credentials_array;
}
// IMPORTANT: Also implement this method!
public function retrieveById($identifier) {
$username = $identifier;
$password = $this->credential_store[$username];
return new User([
'email' => $username,
'password' => $password,
]);
}
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(Authenticatable $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
$username = $credentials['email'];
// Check if user even exists
if (!isset($this->credential_store[$username])) {
return null;
}
$password = $this->credential_store[$username];
return new GenericUser([
'email' => $username,
'password' => $password,
'id' => null,
]);
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->getAuthPassword();
}
}
3. And in app/Providers/AuthServiceProvider:
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
Auth::provider('array', function($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new ArrayUserProvider($app['config']['auth.credentials']);
});
}
}
4. In User.php (model):
class User extends Authenticatable
{
...
public function getAuthIdentifier()
{
return $this->email;
}
}
More Information:
For everyone who is interested, why there need to be the above stated additions:
When you login, the method login in Illuminate\Auth\SessionGuard is called. In this method you will find, that the identifier of the user is stored in the session with $this->updateSession($user->getAuthIdentifier()). Therefore we need to implement this method in our user model.
When you add $this->middleware('auth') in your controller, then the method authenticate() in Illuminate\Auth\Middleware\Authenticate is called. This again calls $this->auth->guard($guard)->check() to check, whether a user is authenticated. The check() method only tests, that there exists a user in the session (see Illuminate\Auth\GuardHelpers). It does this by calling the user() method of the guard, which in this case is again SessionGuard. In the user() method, the user is retrieved by taking the id stored in the session and calling retrieveById to get the user.

Should I re-use controllers in laravel between admin & api ? or have my admin consume my API?

New to laravel and trying to work out the best way to structure my app.
It has both an admin interface and an API (JSON, angularjs front-end).
my routes currently look like:
Route::group(array('prefix' => 'admin', 'before' => 'auth.admin'), function()
{
Route::any('/', array('as' => 'admin.index', function() {
return View::make('admin.index');
}));
Route::resource('countries.products', 'ProductsController');
Route::resource('countries', 'CountriesController');
Route::resource('orders', 'OrdersController');
});
// Route group for API versioning
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('products', 'APIProductsController', array('only' => array('index', 'show')));
Route::resource('orders', 'APIOrdersController', array('only' => array('store', 'update')));
});
There is a lot of duplicated logic in eg, the OrdersController & APIOrdersController. Should I re-use a single controller somehow, maybe with content-negotation? or is it better to modify OrdersController to query the API routes instead of using eloquent?
or is there another, better way?
As I see it, I would extract all object creation logic to a proper class (sounds like a good case for a repository). This class should only know about the parameters it has to receive, and respond accordingly. For example:
class EloquentOrder implements OrderRepositoryInterface {
// Instance of OrderValidator,
// assuming we have one
protected $validator;
public function create($params)
{
// Pseudo-code
$this->validator = new Ordervalidator($params);
if ($this->validator->passes())
create and return new Order
else
return validator errors
}
}
Then, each of your modules can use this functionality inside its controllers.
In your API, you could have this:
class APIOrderController extends APIController {
protected $repository;
public function __construct(OrderRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function create()
{
// Let's imagine you have an APIAuth class which
// authenticates via auth tokens:
if (APIAuth::check()) {
$params = Input::all();
return $this->repository->new($params);
}
return Response::json(['error' => 'You are not authorized to create orders'], 401);
}
}
While in your administration module, you could have:
class AdminOrderController extends AdminController {
protected $repository;
public function __construct(OrderRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function create()
{
// Now, let's imagine Auth uses a different authentication
// method, and can check for specific permissions
if (Auth::check() && Auth::hasPermission('create.orders')) {
$params = Input::all();
return $this->repository->new($params);
}
return Redirect::home()->with('message', 'You are not authorized to create orders');
}
}
As you can see, this allows you to reuse your object creation logic in different contexts. In the example I've used different authentication methods and responses just to show flexibility, but this will really depend on your project requirements.

Categories