Adldap2 / Adldap2-Laravel

LDAP Authentication & Management for Laravel
MIT License
910 stars 184 forks source link

Adldap::auth()->attempt works, while Auth::attempt doesn't. #232

Closed jonathanjuursema closed 7 years ago

jonathanjuursema commented 7 years ago

Hey!

I'm trying to integrate Adldap in a little application that will provide some convenience features for people of my university. I'm looking to integrate their AD/LDAP into the application for convenience.

My first attempt on getting authentication and user management using Adldap working is not succeeding. I'm trying to set-up Adldap so it acts as both an authentication and user provider for Laravel. I am trying to authenticate using username, and I use a custom AuthController.

My current implementation doens't work. Even after inserting valid credentials, it denies them. If I change this line to if (Adldap::auth()->attempt($request->username, $request->password)) { authentication succeeds without any issue.

My university uses multiple username alternatives across both students and staff, and the field I wish to use (which is the only consistent one across account types) is userprincipalname which is in the form username@domain.org where people will supply my application with the username part of the earlier form.

Following are the relevant parts of my .env file referred to in the adldap config files.

LDAP_ACCOUNT_SUFFIX="@domain.org"
LDAP_ADMIN_SUFFIX="@domain.org"
LDAP_BASE_DN="OU=Accounts,DC=ad,DC=domain,DC=org"
LDAP_ADMIN_USERNAME=myaccount
LDAP_ADMIN_PASSWORD=mypassword
LDAP_USERNAME_ATTRIBUTE=userprincipalname
LDAP_LOGIN_ATTRIBUTE=userprincipalname
LDAP_USER_NAME=displayname
LDAP_USER_EMAIL=mail

I'm looking for help getting my implementation to work. :)

stevebauman commented 7 years ago

Hi @jonathanjuursema,

Can you post your adldap_auth.php file, and your user migration table?

jonathanjuursema commented 7 years ago

@stevebauman this is the adldap_auth.php file and this is the user table migration. :)

stevebauman commented 7 years ago

Thanks!

This could be the issue, you are synchronizing an attribute that doesn't exist in your adldap_auth.php config (assuming just a typo):

'sync_attributes' => [
    'name' => env('LDAP_USER_NAME'),

    // Should be: 'email' => env('LDAP_USER_EMAIL'),
    'mail' => env('LDAP_USER_EMAIL')
],

Since your email field isn't nullable in your migration, creating the local user record would fail:

Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('username')->unique();
    $table->string('email');
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

Do you receive any exceptions upon authenticating with the adldap driver?

jonathanjuursema commented 7 years ago

Hey @stevebauman! I was really hoping it was just the typo, but unfortunately. :(

I changed the (what is indeed a) typo in adldap_auth.php, but it still denies authentication. There are no exceptions thrown (neither debug in the browser nor in the Laravel logs) and I neatly see this behaviour happening.

stevebauman commented 7 years ago

Hmm okay, just to double check, the account your authenticating with is located inside the OU you've specified ('Accounts') in your base DN ('OU=Accounts,DC=ad,DC=domain,DC=org'), correct?

Also, can you try running the command (using the username your authenticating with), and see what the output is?:

php artisan adldap:import username@domain.org 
jonathanjuursema commented 7 years ago

I'm postive the user is inside the OU. I use the same settings in another project (Lumen+Adldap) on the same AD and there all AD accounts can be found and accessed.

I've also ran the command and my user is imported successfully. I've also tried various other usernames that my university's AD uses and all of these combination lead to a successful import. I also get all the data I expect in the database in the correct format and fields.

~edit: I also changed the base DN (omitting the OU=Accounts part) which unfortunately changes nothing. :(

stevebauman commented 7 years ago

Ok so definitely configured correctly...

Can you create a listener for the event Adldap\Laravel\Events\DiscoveredWithCredentials:

https://github.com/Adldap2/Adldap2-Laravel/blob/master/src/Traits/AuthenticatesUsers.php#L103

And see if it is fired?

jonathanjuursema commented 7 years ago

I changed the following (I'm not particularly familiar with using events, so to double check):

In EventServiceProvider:

    ...
    protected $listen = [
        'Adldap\Laravel\Events\DiscoveredWithCredentials' => [
            'App\Listeners\TestListener',
        ],
    ];
    ...

In TestListener.php:

<?php

namespace App\Listeners;

use App\Events\Adldap\Laravel\Events\DiscoveredWithCredentials;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class TestListener
{
    public function __construct()
    {
        //
    }

    public function handle(DiscoveredWithCredentials $event)
    {
        Log::info('test event fired!!!');
        Lof::info($event)
    }
}

Unfortunately, nothing turns up in the logfile after a few log-in attempts.

jonathanjuursema commented 7 years ago

In the meantime I also tried running the code against a different AD to make sure it was not the university AD causing the problems, but I encountered the same problem.

stevebauman commented 7 years ago

Your configuration for the listener is correct, and when you try authenticate again, it should show in the log that the user your authenticating with has been discovered. If nothing appears in the log, then we can troubleshoot from there.

jonathanjuursema commented 7 years ago

Thanks for the confirmation! With the event configuration in place both authentication syntaxes (Adldap::auth()->attempt and Auth::attempt) don't result in a log-entry, while the former still results in a pass (redirect to homepage) and the latter still in a fail (the configured error message).

You already have my thanks for the assistance. :)

stevebauman commented 7 years ago

No problem at all. :)

In the top of your routes file, can you try locating the user you're looking for and see if the user is returned?:

// routes/web.php

$user = \Adldap\Laravel\Facades\Adldap::search()->users()->findBy('userprincipalname', 'username@domain.org');

dd($user);
jonathanjuursema commented 7 years ago

Using your snippet the correct user is returned.

stevebauman commented 7 years ago

Strange... Can you run php artisan config:clear and try re-authenticating to be sure?

Also, are you using dev-master of Adldap2-Laravel?

Edit: miss-clicked close issue

jonathanjuursema commented 7 years ago

php artisan config:clear didn't solve the problem. I have currently configured version 2.1.* in composer, but the problem also persists on dev-master.

designvoid commented 7 years ago

Same issue here also. The snippet above does return the correct user.

sbk123 commented 7 years ago

Hi Steve,

i am using "adldap2/adldap2-laravel": "3.0.0" and facing the same issue( Adldap::auth()->attempt works, while Auth::attempt() doesn't.

And moreoever i am able to import the user when i execute the following command php artisan adldap:import username@domain.org

Thanks

struyfv commented 7 years ago

Hi,

I have the same issue. And the import also works in my application.

Thanks for taking a look

stevebauman commented 7 years ago

@jonathanjuursema, can you post your 2014_10_12_000000_create_users_table.php migration?

Edit: Realized I asked you for this already, but not sure if you've moved to v3.0.0 yet.

Configuration is 100% the issue here. There seems to be conflicting attributes looking at your .env file you've posted. For example, you've included an account suffix, and want to authenticate users by username, but you're actually using users userprincipalname, not their samaccountname. A users userprincipalname contains this domain suffix already (for example sbauman@corp.acme.org). While a samaccountname is only their username (sbauman - no suffix).

This configuration below, would actually try authenticating users against your server with the username sbauman@domain.org@domain.org, which is obviously going to fail.

You're going to be able to connect successfully however, because your admin username doesn't contain this suffix, so it's authenticated correctly using myaccount@domain.org.

LDAP_ACCOUNT_SUFFIX="@domain.org"
LDAP_ADMIN_SUFFIX="@domain.org"
LDAP_BASE_DN="OU=Accounts,DC=ad,DC=domain,DC=org"
LDAP_ADMIN_USERNAME=myaccount
LDAP_ADMIN_PASSWORD=mypassword
LDAP_USERNAME_ATTRIBUTE=userprincipalname
LDAP_LOGIN_ATTRIBUTE=userprincipalname
LDAP_USER_NAME=displayname
LDAP_USER_EMAIL=mail

To move forward I need to know if you've updated to v3.0 or not.

For other users commenting here with the same issue (@struyfv, @sbk123, @designvoid), please check your adldap.php / adldap_auth.php and Laravel configuration, as it's related to 99% of all issues created here.

You can easily verify what's going on by simply dumping variables during the life cycle of the authentication request (for example, diving into vendor/Adldap2/Adldap2-Laravel/src/Auth/Resolver and dd()ing variables). This will help you troubleshoot configuration issues and know exactly what's going on.

In regards to the title of the post, calling Adldap::auth()->attempt() and Auth::attempt() are 100% completely separate operations.

Calling Adldap::auth()->attempt() simply makes a ldap_bind() request to your server.

Calling Auth::attempt() performs a ton more operations:

  1. Calls your current auth provider and asks to retrieve a user by their credentials
    1. Which connects to your configured AD provider using your admin username / password
    2. Tries to locate the user by your configured resolver
      1. If an AD user is found, it creates an instance of your configured User model using your configured importer - which also sets attributes that you need sync'ed Importer
  2. If a user instance is created successfully, then the users credentials are attempted against your configured LDAP provider (ldap_bind)
  3. If authentication passes, the created User model instance then has its password set, and it is then saved. true is then returned to the Laravel auth manager, which indicates that it should persist the returned User model to the session.

As you can see, there is much more complexity to these two methods, and they shouldn't be compared against.

jonathanjuursema commented 7 years ago

@stevebauman thanks for the detailed response! I have since December moved on (after struggling with this for a little I had no time for it anymore, and a friend of mine took over the concept), sorry for not letting you know. I'm hoping this answer also helps @designvoid, @sbk123 and @struyfv. I'm not closing my own issue because I'm not sure the other people that replied here have additional questions, but if none of them have please consider this issue closed for me.

Thanks again for your time!