Closed lela2011 closed 7 months ago
Hi @lela2011! Thanks so much for the sponsorship ❤️
I can definitely help you get to the bottom of this 👍
Can you confirm that you can connect successfully to your LDAP server by running the php artisan ldap:test
command?
Can you confirm after you login with an LDAP user that a session is properly generated with your configured session.driver
(config/session.php
file)? This is set to file
by default, so check in the storage/framework/sessions
folder after logging in successfully to see if a new file is created. If a new file is created, open it, and make sure you see your users uid
inside.
Can you confirm if you see any error log entries in your storage/logs
folder after authenticating?
Dear @stevebauman
$ php artisan ldap:test
and it says that it successfully connected.User [xxx] has successfully passed LDAP authentication.
and User [xxx] has successfully authenticated.
I suppose I have to setup session.php
somehow?
Thanks for checking all of that @lela2011!
Can you try removing $request->session()->regenerate()
inside of your controller and try authenticating again to see if it's possibly a session regeneration issue?
@stevebauman That unfortunately didn't work. The session-file still doesn't have the uid in it and the @auth functionality is not working.
@stevebauman These are my routes:
<?php
use App\Http\Controllers\HomeController;
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::get('/', [HomeController::class, 'home']);
Route::get('/test', [HomeController::class, 'test']);
Route::post('/authenticate', [UserController::class, 'authenticate']);
This is my session.php file
<?php
use Illuminate\Support\Str;
return [
'driver' => env('SESSION_DRIVER', 'file'),
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
'encrypt' => false,
'files' => storage_path('framework/sessions'),
'connection' => env('SESSION_CONNECTION'),
'table' => 'sessions',
'store' => env('SESSION_STORE'),
'lottery' => [2, 100],
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
'path' => '/',
'domain' => env('SESSION_DOMAIN'),
'secure' => env('SESSION_SECURE_COOKIE'),
'http_only' => true,
'same_site' => 'lax',
];
I saw somewhere that I would have to set middleware but I haven't done that in the test project what uses DB-Authentication.
Does the LDAP user you login with have their database record synchronized upon login? And is the uid
column filled with their uid
?
I saw somewhere that I would have to set middleware but I haven't done that in the test project what uses DB-Authentication.
You only need the auth
middleware if you want to protect a route from guest users 👍
Have you modified your RouteServiceProvider
by chance and removed the web
middleware?
@stevebauman Could you maybe give me a few more details what I would have to do to check that? I am syncing uid, first name and surname. At least that's how I read my auth.php file.
Yea for sure -- just place this in your routes/web.php
file at the top, refresh your application, and see if any dumped user exists with the uid
you signed in with:
dd(\App\Models\User::all());
@stevebauman
Have you modified your
RouteServiceProvider
by chance and removed theweb
middleware?
I checked. That one is still the same as the default laravel config
@stevebauman
Yea for sure -- just place this in your
routes/web.php
file at the top, refresh your application, and see if any dumped user exists with theuid
you signed in with:
This is what it says. And if I check the database ther also is a user in there with the uid
Illuminate\Database\Eloquent\Collection {#1079
#items: array:1 [
0 => App\Models\User {#1035
#connection: "mysql"
#table: "users"
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
+preventsLazyLoading: false
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#escapeWhenCastingToString: false
#attributes: array:13 [
"uid" => "xxx"
"first_name" => "xxx"
"last_name" => "xxx"
"xxx" => null
"xxx" => null
"xxx" => null
"xxx" => null
"password" => "xxx"
"remember_token" => null
"created_at" => "2023-11-15 16:04:57"
"updated_at" => "2023-11-15 16:04:57"
"guid" => "xxx"
"domain" => "default"
]
#original: array:13 [
"uid" => "xxx"
"first_name" => "xxx"
"last_name" => "xxx"
"xxx" => null
"xxx" => null
"xxx" => null
"xxx"=> null
"password" => "xxx"
"remember_token" => null
"created_at" => "2023-11-15 16:04:57"
"updated_at" => "2023-11-15 16:04:57"
"guid" => "xxx"
"domain" => "default"
]
#changes: []
#casts: array:2 [
"email_verified_at" => "datetime"
"password" => "hashed"
]
#classCastCache: []
#attributeCastCache: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#guarded: array:1 [
0 => "*"
]
#rememberTokenName: "remember_token"
}
]
#escapeWhenCastingToString: false
}
@stevebauman I figured out that the authentication is lost once a redirect happens. As long as I don't direct away from my authenticate route there isn't a problem.
@stevebauman After further debugging I found 2 more things.
dd(Auth::user());
returns the user if it's called before the redirectdd(Auth::id());
returns null if it's called before the redirectMight that be the issue?
Yea that's definitely an issue @lela2011, can you try adding this method to your extended app/Ldap/User.php
model and then try logging in?
public function getAuthIdentifier(): ?string
{
return $this->getFirstAttribute($this->guidKey);
}
@stevebauman this solved the issue. Additionally I also had to make sure that protected $primaryKey = "uid"
and public $incrementing = false;
was set.
I have one more question though:
The user table will be accessed by another php script to pull certain fields. I have some predefined data from a csv that I would like to add to the users table before the user is signed in. If I prepopulate the table with for example uid
, first_name
, last_name
and then try to sign in I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'xxx' for key 'PRIMARY'
Is there any way to avoid that error?
That's great to hear @lela2011! I'll create a bug fix for the getAuthIdentifier()
method.
In regards to your question, that error means a user already exists in the database with the given uid
. You'll have to use the firstOrCreate
method to ensure you don't attempt creating a new database entry for a user that already exists with that uid
:
use App\Models\User;
// The first array will be merged with
// the second if the user doesn't exist.
$user = User::firstOrCreate(['uid' => $uid], [
'first_name' => $firstName,
'last_name' => $lastName,
]);
@stevebauman where would I have to put that code? In the AuthController?
Oh sorry I misread your previous comment. If you have existing users in your database that you would like to synchronize with, use the sync_existing
option in your auth.php
configuration:
https://ldaprecord.com/docs/laravel/v3/auth/database/configuration/#sync-existing-records
// config/auth.php
return [
// ...
'providers' => [
'users' => [
'driver' => 'ldap',
'model' => App\Ldap\User::class,
'rules' => [
App\Rules\OnlyXXX::class,
],
'scopes' => [],
'database' => [
'model' => App\Models\User::class,
'sync_passwords' => false,
'sync_attributes' => [
'uid' => 'uid',
'first_name' => 'givenName',
'last_name' => 'sn',
],
'sync_existing' => ['uid' => 'uid'], // <-- Added here.
],
],
],
],
@stevebauman Alright. That makes sense. I forgot about that option. I opted for a second 'user-profile' table with a 1:1 relation but I might rollback and implement this. I have one last question though. How would you create something like an admin user? The user should also login with LDAP but then be able to edit everyone's data and not just their own. Preferably they should should also be able to set additional administrators.
Sounds good!
In regards to your question, you can use an attribute handler to set a flag on your User
model, or create an event listener to assign a role to the user after its synchronized via the php artisan ldap:import
command (the way you implement a role/permission system is up to you of course):
https://ldaprecord.com/docs/laravel/v3/auth/configuration/#attribute-handlers
Using an Attribute Handler:
namespace App\Ldap;
use App\Ldap\User as LdapUser;
use App\Ldap\Group as LdapGroup;
use App\Models\User as DatabaseUser;
class AttributeHandler
{
public function handle(LdapUser $ldap, DatabaseUser $database)
{
$database->is_admin = $ldap->isDescendantOf('ou=Administrators,dc=local,dc=com');
}
}
Using an Event Listener on Import Saved
event:
use App\Models\Role;
use LdapRecord\Models\OpenLDAP\Group;
use LdapRecord\Laravel\Events\Import\Saved;
class SyncRole
{
public function handle(Saved $event)
{
$administrators = Group::findOrFail(
'cn=Administrators,dc=local,dc=com',
);
if ($event->object->groups()->exists($administrators)) {
$event->eloquent->roles()->syncWithoutDetaching(
Role::firstWhere('administrators')
);
}
}
}
Closing this out now as your original issue has been resolved! 🙏
Environment:
Dear @stevebauman,
First of all thank you for this great library. After struggling a bit and some wrong information on my side I was able to authenticate a user against the university's OpenLDAP-Server. This is my code:
ldap.php:
auth.php:
Overridden Group.php for Rules:
Overridden LDAPRecord User.php:
DB-User-Model:
UserController:
Users-Table-Migration:
I had to override the User and Group of the library cause else the LDAP-Filter didn't work. In my home route I use
For some reason I always see the login screen as if the application is not aware of the fact that the user is signed in. Can you help me figure this out? I don't have any other settings changed in the app. Or not that I would have done it consciously.