Laravel LDAP (Adldap2) Can't authenticate,username is null into guard->attempt - laravel

Morning,
I keep getting the message "A username must be specified." when trying to login my app.
Connection to LDAP is OK, sync also, in my database/table users i see all username with password.
But can't login with anyone username.
Trying to dd($username) into LoginController, "guard()->attempt" show me "null".
Thanks for your help !
My version
Laravel Version: ^7.0
Adldap2-Laravel Version: ^6.1
PHP Version: ^7.2.5
LDAP Type: OpenLDAP
my .env
LDAP_HOSTS=ldap.forumsys.com
LDAP_BASE_DN=dc=example,dc=com
LDAP_USERNAME=cn=read-only-admin,dc=example,dc=com
LDAP_PASSWORD=password
LDAP_PASSWORD_SYNC=true
my ldap.php
<?php
return [
'logging' => env('LDAP_LOGGING', false),
'connections' => [
'default' => [
'auto_connect' => env('LDAP_AUTO_CONNECT', true),
'connection' => Adldap\Connections\Ldap::class,
'settings' => [
'schema' => Adldap\Schemas\OpenLDAP::class,
'account_prefix' => env('LDAP_ACCOUNT_PREFIX', ''),
'account_suffix' => env('LDAP_ACCOUNT_SUFFIX', ''),
'hosts' => explode(' ', env('LDAP_HOSTS', 'corp-dc1.corp.acme.org corp-dc2.corp.acme.org')),
'port' => env('LDAP_PORT', 389),
'timeout' => env('LDAP_TIMEOUT', 5),
'base_dn' => env('LDAP_BASE_DN', 'dc=corp,dc=acme,dc=org'),
'username' => env('LDAP_USERNAME', 'username'),
'password' => env('LDAP_PASSWORD', 'secret'),
'follow_referrals' => false,
'use_ssl' => env('LDAP_USE_SSL', false),
'use_tls' => env('LDAP_USE_TLS', false),
],
],
],
];
my ldap_auth
<?php
return [
'connection' => env('LDAP_CONNECTION', 'default'),
'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class,
'model' => App\User::class,
'rules' => [
// Denys deleted users from authenticating.
Adldap\Laravel\Validation\Rules\DenyTrashed::class,
// Allows only manually imported users to authenticate.
// Adldap\Laravel\Validation\Rules\OnlyImported::class,
],
'scopes' => [
// Only allows users with a user principal name to authenticate.
// Suitable when using ActiveDirectory.
// Adldap\Laravel\Scopes\UpnScope::class,
// Only allows users with a uid to authenticate.
// Suitable when using OpenLDAP.
// Adldap\Laravel\Scopes\UidScope::class,
],
'identifiers' => [
'ldap' => [
'locate_users_by' => 'uid',
'bind_users_by' => 'distinguishedname',
],
'database' => [
'guid_column' => 'objectguid',
'username_column' => 'username', //'email',
],
'windows' => [
'locate_users_by' => 'samaccountname',
'server_key' => 'AUTH_USER',
],
],
'passwords' => [
'sync' => env('LDAP_PASSWORD_SYNC', false),
'column' => 'password',
],
'login_fallback' => env('LDAP_LOGIN_FALLBACK', false),
'sync_attributes' => [
//'email' => 'userprincipalname',
'username' => 'uid',
'name' => 'cn',
],
'logging' => [
'enabled' => env('LDAP_LOGGING', true),
'events' => [
\Adldap\Laravel\Events\Importing::class => \Adldap\Laravel\Listeners\LogImport::class,
\Adldap\Laravel\Events\Synchronized::class => \Adldap\Laravel\Listeners\LogSynchronized::class,
\Adldap\Laravel\Events\Synchronizing::class => \Adldap\Laravel\Listeners\LogSynchronizing::class,
\Adldap\Laravel\Events\Authenticated::class => \Adldap\Laravel\Listeners\LogAuthenticated::class,
\Adldap\Laravel\Events\Authenticating::class => \Adldap\Laravel\Listeners\LogAuthentication::class,
\Adldap\Laravel\Events\AuthenticationFailed::class => \Adldap\Laravel\Listeners\LogAuthenticationFailure::class,
\Adldap\Laravel\Events\AuthenticationRejected::class => \Adldap\Laravel\Listeners\LogAuthenticationRejection::class,
\Adldap\Laravel\Events\AuthenticationSuccessful::class => \Adldap\Laravel\Listeners\LogAuthenticationSuccess::class,
\Adldap\Laravel\Events\DiscoveredWithCredentials::class => \Adldap\Laravel\Listeners\LogDiscovery::class,
\Adldap\Laravel\Events\AuthenticatedWithWindows::class => \Adldap\Laravel\Listeners\LogWindowsAuth::class,
\Adldap\Laravel\Events\AuthenticatedModelTrashed::class => \Adldap\Laravel\Listeners\LogTrashedModel::class,
],
],
];
my loginController
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function username()
{
return 'username';
}
}

Been through this pain myself.
It looks like you are connecting to LDAP with your service account, but I don't see where you are logging the authenticated user into Laravel. Do your users have to supply a username and password on a screen somewhere? If not, how do you grab the user and log them in to Laravel?
The way I did this in the businesses that had to go through LDAP was to have the normal Laravel login page, but a middle method within the login controller which sent a message to LDAP through the service account with the PW. If this succeeded, then login with Laravel's standard method. Basically just check the PW against LDAP and then log in rather than checking against the Laravel DB.
Example code - this will vary wildly but can give you an idea of what might work:
if(env('LOGIN', false) === 'LDAP'){
$ldap = new \App\Http\Controllers\ClientSpecific\BaseLDAPController();
$username = $request->input('username');
if($ldap->authenticate($username, $request->input('password'))){
return $this->sendLoginResponse($request);
}
}else {
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
return $this->sendLoginResponse($request);
}
}

I know this is an old question, but I was still facing this issue even in 2020 and it took a lot of my time. Actually the problem is very silly if you are also trying to bind an OpenLDAP server with your Laravel application using Adldap2-Laravel package.
Adldap2-Laravel is by default configured for Microsoft's Active Directory. But for OpenLDAP, we need to properly change the Identifiers array as follows..
<?php
return [
// configurations settings...
'identifiers' => [
'ldap' => [
'locate_users_by' => 'uid', // changed from userprincipalname
'bind_users_by' => 'dn', // changed from distinguishedname
],
'database' => [
'guid_column' => 'objectguid',
'username_column' => 'username', //'email',
],
'windows' => [
'locate_users_by' => 'samaccountname',
'server_key' => 'AUTH_USER',
],
],
// rest of the configurations...
];
Do tell me if my answer is not clear for new comers since I'm also a newbie. I'll try to explain the solution further better :)

Related

Laravel: How to set my custom password broker to be used by Password::sendResetLink?

The documentation of Laravel says:
You may be wondering how Laravel knows how to retrieve the user record from your application's database when calling the Password facade's sendResetLink method. The Laravel password broker utilizes your authentication system's "user providers" to retrieve database records. The user provider used by the password broker is configured within the passwords configuration array of your config/auth.php configuration file. To learn more about writing custom user providers, consult the authentication documentation
But how to set my custom password broker users:foo to be used by Password::sendResetLink in my custom package packages/custom-packages/foo/src/routes/api.php?
Route::post('forgot-password', function (Request $request) {
$request->validate(['email' => 'required|email|exists:foo.users,email']);
$status = Password::sendResetLink(
$request->only('email')
);
return $status === Password::RESET_LINK_SENT
? response()->json(['status' => __($status)])
: response()->json(['email' => __($status)]);
});
Below is my config/auth.php:
...
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
'users:foo' => [
'provider' => 'users:foo',
'table' => 'foo.password_resets',
'expire' => 60,
'throttle' => 60,
]
]
Sets your broker through the facade.
use Illuminate\Support\Facades\Password;
$broker = 'users:foo'; // the default value is 'users' from 'auth.defaults.passwords'.
Password::broker($broker); // sets the given broker
It will get config from "auth.passwords.{$name}" where $name is $broker.
If the config of the broker is undefined then throws the exception: "Password resetter [{$name}] is not defined.". In the good way will be created a new password broker with your config.

Laravel: Set a cookie on successful login using a custom guard/attempt method

I am using a third party database for authentication. Everything is working great but now would like to set a cookie when a user has logged in.
As stated in the Laravel Docs:
The attempt method will return true if authentication was successful. Otherwise, false will be returned.
This is what I am doing in my controller:
MyLoginController.php
$user = Auth::guard('foo')->attempt(['userid' => $request->username, 'password' => $request->password], $request->remember);
dd($user);
...
return redirect()->intended(route('home'));
Everything here is great. I'm getting true or false back as expected.
What I am trying to do is if the login is successful, set a cookie on the response. I need the user object back to get a value from. Something like this:
MyLoginController.php
$user = Auth::guard('foo')->attempt(['userid' => $request->username, 'password' => $request->password], $request->remember);
if ($user) {
switch (App::environment()) {
case 'local':
$cookie = cookie('localCookieName', $user->token, 480);
break;
case 'development':
$cookie = cookie('devCookieName', $user->token, 480);
break;
case 'production':
$cookie = cookie('cookieName', $user->token, 480);
break;
default:
//
break;
}
return redirect()->intended(route('home'))->cookie($cookie);
}
return redirect()->intended(route('home'));
I am using a custom User Provider to authenticate my users - everything there is working great as well. I am getting the user, and saving any data to my local db if needed. I thought I might be able to just set the cookie in the UserProvider, but without doing ->cookie($cookie) nothing is getting set.
The value of $user->token is coming back from my 3rd party authentication. So that's why I need to be able to access that value.
Reading the docs, it looks like I need to be setting cookie(s) on the response ->cookie($cookie) or withCookies($cookies).
This leads me to believe I need to set the cookie on my controller, but I'm not sure how to get the user object back since the attempt method only returns true or false.
How can I get the user object from within the attempt method? Maybe I am making thins incredibly difficult for myself and there is an easier way to set the cookie?
Thank you for any suggestions!
EDIT
Here is my config/auth.php file:
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'foo' => [
'driver' => 'session',
'provider' => 'foo',
],
],
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'foo' => [
'driver' => 'foo', // Using a 3rd party for auth.
'model' => App\MyUser::class, // User model for auth.
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
attempt does a login if the credentials are valid and correct for a User. So you can just get the user from the Request or the Auth guard, since they are logged in:
$user = $request->user();
$user = $request->auth('foo')->user();
$user = Auth::guard('foo')->user();
...
If you know that attempt passed, the User is also available via getLastAttempted on the session guard:
$user = Auth::guard('foo')->getLastAttempted();
Although you can use that I would not, as you have to check that attempt actually returned true before trusting this value. This holds the last user retrieved by credentials, which could not have been authenticated potentially, attempt returned false.
You do not have to directly be adding a cookie to the Response. In the Cookie section of the docs should be information about "queue"ing a cookie to automatically be attached to the outgoing Response:
Cookie::queue('name', 'value', $minutes);
Laravel 6.x Docs - Responses - Attaching Cookies to Responses

how to stop execution of ctp file in cakephp 2.x after validating the url

In my CakePHP application, I have applied Url validations so that admin can access only those actions which are defined for admin and same as with users.
In my application, "surveylist" is the action of admin and when any user directly access that action(surveylist), URL validations work(Unauthorized access msg is displayed).
But below that message ctp file of surveylist executes forcefully and show errors because I have validated URL through the try-catch block and it cannot get the set variables of action.
I want that ctp file should not execute if unauthorize error comes.
My code for surveylist is:-
public function surveylist($pg=null){
try{
if($this->checkPageAccess($this->params['controller'] . '/' . $this->params['action'])){
$this->Paginator->settings = array(
'Survey' => array(
'limit' => 5,
'order' => 'created desc',
'conditions'=>array('is_deleted'=> 0),
'page' => $pg
)
);
$numbers = $this->Paginator->paginate('Survey');
$this->set(compact('numbers'));
}else{
$this->Flash->set(__('Unauthorised access'));
}
}catch(Exception $e){
$this->Flash->set(__($e->getMessage()));
}
}
I don't want the ctp file of surveylist to execute if control comes to else.
Plz, help me out......
Thanx in advance...
I suppose you are using prefix to separate admin and users, if not please do that it is great way to handle and restrict methods.
After doing that you have to make condition to check which prefix(admin, user) is currently active and according that load Auth component and allow action in allow() method of Auth.
Example:
$this->loadComponent('Auth',[
/*'authorize' => [
'Acl.Actions' => ['actionPath' => 'controllers/']
],*/
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index'
],
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => [
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authError' => 'You are not authorized to access that location.',
]);
if ($this->request->params['prefix']=='admin') {
// Put actions you want to access to admin in allow method's array
$this->Auth->allow(array('add', 'edit', etc...));
} else if ($this->request->params['prefix']=='user') {
// Put actions you want to access to user in allow method's array
$this->Auth->allow(array('login', 'view', etc...));
}
This way you can restrict actions for particular role.
Hope this helps!

Yii2 Restful API - add possibility to auth with session

In my Restful API project I use Bearer Token Authentication.
But now there is need to use Session Authentication in only one Controller to perform special actions with files. But I don't really understand how I should change behaviors method for that. What I have done,
'enableSession' => true
My behaviors method looks like:
public function behaviors() {
$behaviors = parent::behaviors();
unset($behaviors['authenticator']);
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['options'],
];
$behaviors['access'] = [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'actions' => [
'options',
],
],
[
'actions'=>['content'],
'allow' => true,
'roles' => ['admin'],
]
],
];
return $behaviors;
}
I think there should be some changes in authenticator settings, use something else except HttpBearerAuth or may be something else, please help

yii2 Unable to verify your data submission

I use nginx, PHP 5.5.14, php-fpm, yii2, mac os.
I changed yii config to store session in database (postgress, user is superuser).
This is in my config:
'session' => [
'class' => 'yii\web\DbSession',
'sessionTable' => 'session',
],
And now when I try to register new user I've got this error:
Bad Request (#400)
Unable to verify your data submission
Here is a part of log:
10 17:25:57.434 info yii\db\Command::query SELECT "data" FROM "session" WHERE "expire">1425993957 AND "id"='cfg9sutufqchr1tdose4cack15'
/Users/pupadupa/Dev/www/mint-office-web/components/Controller.php (41)
11 17:25:57.442 info yii\web\Session::open Session started
/Users/pupadupa/Dev/www/mint-office-web/components/Controller.php (41)
12 17:25:57.450 error yii\web\HttpException:400 exception 'yii\web\BadRequestHttpException' with message 'Не удалось проверить переданные данные.' in /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/web/Controller.php:110
Stack trace:
#0 /Users/pupadupa/Dev/www/mint-office-web/components/Controller.php(41): yii\web\Controller->beforeAction(Object(app\controllers\user\RegistrationAction))
#1 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Controller.php(149): app\components\Controller->beforeAction(Object(app\controllers\user\RegistrationAction))
#2 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Module.php(455): yii\base\Controller->runAction('registration', Array)
#3 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/web/Application.php(83): yii\base\Module->runAction('user/registrati...', Array)
#4 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Application.php(375): yii\web\Application->handleRequest(Object(yii\web\Request))
#5 /Users/pupadupa/Dev/www/mint-office-web/web/index.php(20): yii\base\Application->run()
#6 {main}
13 17:25:57.454 trace yii\base\Controller::runAction Route to run: index/error
BTW,
I've got <?= Html::csrfMetaTags() ?> in head section and I have csrf input in my form. So it seems not the problem
I don't want to do public $enableCsrfValidation = false; because I think it's not solution but workaround.
How could I understand what cause this error?
As I mentioned earlier problem only appears when I store session in database.
Some additional information:
I could set and get variable from session. For example, I put it in beoreAction of Controller.php
Yii::$app->session->set('test', 'qwe');
$t = Yii::$app->session->get('test') ;
var_dump($t);
But after that If I comment first line like this
//Yii::$app->session->set('test', 'qwe');
$t = Yii::$app->session->get('test') ;
var_dump($t);
and refresh the page - I recieve NULL (BTW I could see Cookie:PHPSESSID=cfg9sutufqchr1tdose4cack15 in cookies after refresh).
So it seems there are some problems with session (DbSession) or maybe my php/php-fpm/nginx settings.
My UserController.php:
<?php
namespace app\controllers;
use app\components\Controller;
use app\models\Client;
use app\models\User;
use yii\filters\AccessControl;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
class UserController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'employee'],
'rules' => [
[
'actions' => ['logout',],
'allow' => true,
'roles' => ['#'],
],
[
'actions' => ['employee', 'employee_add'],
'allow' => true,
'roles' => [ROLE_CLIENT_ADMIN],
],
],
],
];
}
public function actions()
{
return [
'login' => 'app\controllers\user\LoginAction',
'logout' => 'app\controllers\user\LogoutAction',
'restore' => 'app\controllers\user\RestoreAction',
'registration' => 'app\controllers\user\RegistrationAction',
'employee' => 'app\controllers\user\EmployeeAction',
'employee_add' => 'app\controllers\user\EmployeeAddAction',
//'passwd' => 'app\controllers\user\PasswdAction',
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV === 'dev' ? 'testme' : null,
],
];
}
/**
* #param int $clientId
* #return Client
* #throws BadRequestHttpException
* #throws NotFoundHttpException
*/
public function getClient($clientId)
{
if (!\Yii::$app->user->can(ROLE_ADMIN)) {
/* #var User $user */
$user = \Yii::$app->user->identity;
$clientId = $user->client_id;
}
if (!$clientId) {
throw new BadRequestHttpException('Bad request');
}
$client = Client::find()->where(['id' => $clientId])->one();
if (!$client) {
throw new NotFoundHttpException('Компания не найдена');
}
return $client;
}
}
My RegistrationAction.php:
<?php
namespace app\controllers\user;
use app\models\UserConfirm;
use Yii;
use app\components\Action;
use app\forms\RegistrationForm;
use yii\web\NotFoundHttpException;
class RegistrationAction extends Action
{
public function run($key = null)
{
if ($key !== null) {
/* #var UserConfirm $confirm */
$confirm = UserConfirm::find()->andWhere('expire > NOW()')->andWhere([
'key' => $key,
'action' => 'reg'
])->one();
if (!$confirm) {
throw new NotFoundHttpException('Key not found');
}
$user = $confirm->user;
$user->enabled = true;
$user->last_login = date('Y-m-d H:i:s');
$user->save();
$confirm->delete();
Yii::$app->user->login($user, 0);
return $this->controller->goHome();
}
$model = new RegistrationForm();
if ($model->load($_POST) && $model->validate() && $model->register()) {
$subject = Yii::$app->name . ' - Success';
$message = $this->controller->renderPartial(
'//email/registration',
[
'username' => $model->email,
'password' => $model->password,
'key' => $model->key,
'keyExpire' => $model->keyExpire
]
);
$res = Yii::$app->mailer->compose()
->setTo($model->email)
->setFrom([Yii::$app->params['from'] => Yii::$app->params['fromName']])
->setSubject($subject)
->setHtmlBody($message)
->send();
Yii::$app->session->setFlash('registrationFormSubmitted');
return $this->controller->refresh();
}
return $this->controller->render('registration', ['model' => $model]);
}
}
Looks like your session id changes. Check if the value of the session cookie changes after a second request. This happens when you misconfigure the domain that is set for the session cookie, make sure it matches the hostname in your URL.
I think this happens atimes when the server is messed up, all I needed to do was to restart my apache server.
For apache on MacOSx
sudo apachectl restart
For apache on Linux
sudo service apache2 restart
or
sudo service httpd restart
i hv used this while calling logout
$menuItems[] = [
'label' => 'Logout (' . Yii::$app->user->identity->username . ')',
'url' => ['/site/logout'],
// 'linkOptions' => ['data-method' => 'get']
'data-method' => 'get'
];
in main .php change post to get
---------------------------------------------------
and have changes this also
$url = Html::a('Logout',
['/site/logout'],
['class' => 'btn btn-success', 'data-method' => 'get']);
this is also in main.php
--------------------------------------------------------------------
in behaviour of site controller also
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'signup','index'],
'rules' => [
[
'actions' => ['signup'],
'allow' => true,
'roles' => ['?'],
],
[
'actions' => ['logout','index'],
'allow' => true,
'roles' => ['#'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['get'],
],
],
];
}
change post to get...and it will work....
What I did is, I just make the "csrfParam" different for both backend and frontend and after "php init" command everything is now working fine.
Not sure whether its a bug or we must have to set different "csrfParam" in case if we use DBsession.
Not sure if it's late, but I found this solution which worked for me in a similar case of Yii2 framework spitting 400 Bad Request at POST form requests.
Simply add this code in your form to manually include a CSRF input field for the key and Yii2 will accept the data submission:
<?php
use Yii;
echo Html::tag('input', '', ['type' => 'hidden', 'name' => '_csrf-backend', 'value' => Yii::$app->request->getCsrfToken()]);
?>
Note: this is for the advanced template. In the long run I suggest not to use a framework that bases on short tags as PHPers would know its a curse.

Resources