Adldap2 / Adldap2-Laravel

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

Howto: LDAP authentification in Laravel using Active Directory sAMAccounyName #795

Closed shabeebrizvi closed 5 years ago

shabeebrizvi commented 5 years ago

Description: WORKING CODE

I am using the following link as tutorial for LDAP Authenticate https://github.com/jotaelesalinas/laravel-simple-ldap-auth. But instead of OpenLDAP, I am trying to authenticate via Active Directory using sAMAccountName (firstname.lastname). I had some hick-up initially but have replaced my code with working. Hope this will help someone!

Steps To Reproduce:

.env

LDAP_SCHEMA=ActiveDirectory
LDAP_HOSTS=10.12.123.456 // to be replaced
LDAP_BASE_DN=dc=domain,dc=net // to be replaced
LDAP_USER_ATTRIBUTE=sAMAccountName
LDAP_USER_FORMAT=%s@userEmail.com // to be replaced
LDAP_CONNECTION=default

LDAP_ADMIN_USERNAME=`username` // NO DOT, but for user its firstname dot lastname
LDAP_ADMIN_PASSWORD=`password`

database/migrations/2014_10_12_000000_create_users_table.php

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

config/ldap.php

'connections' => [

    'default' => [
        'auto_connect' => env('LDAP_AUTO_CONNECT', false),
        'connection' => Adldap\Connections\Ldap::class,
        'settings' => [
            'schema' => Adldap\Schemas\ActiveDirectory::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_ADMIN_USERNAME', ''),
            'password' => env('LDAP_ADMIN_PASSWORD', ''),
            'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
            'use_ssl' => env('LDAP_USE_SSL', false),
            'use_tls' => env('LDAP_USE_TLS', false),
        ],
    ],

],

config/ldap_auth.php

'<?php

return [

    'connection' => env('LDAP_CONNECTION', 'default'),

    'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class,

    'model' => App\User::class,

    'rules' => [
        Adldap\Laravel\Validation\Rules\DenyTrashed::class,
    ],

    'scopes' => [
    ],

    'identifiers' => [

        'ldap' => [
            'locate_users_by' => 'sAMAccountName',
            'bind_users_by' => 'distinguishedname',
        ],

        'database' => [
            'guid_column' => 'objectguid',
            'username_column' => 'username',
        ],

        '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),

    // Database Column => Active Directory 
    'sync_attributes' => [
        'name' => 'cn', 
        'username' => 'sAMAccountName',
        'email' => 'userPrincipalName',
    ],

    '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,
        ],
    ],

];

app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use Adldap\Laravel\Facades\Adldap;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    public function username()
    {
        return 'username';
    }

    protected function validateLogin(Request $request)
    {
        $this->validate($request, [
            $this->username() => 'required|string|regex:/^[A-Za-z]+\.[A-Za-z]+$/',
            'password' => 'required|string',
        ]);
    }

    protected function attemptLogin(Request $request)
    {

        $credentials = $request->only($this->username(), 'password');
        $username = $credentials[$this->username()];
        $password = $credentials['password'];

        $user_format = env('LDAP_USER_FORMAT');
        $userdn = sprintf($user_format, $username);

        if (Adldap::auth()->attempt($userdn, $password, $bindAsUser = true)) {

            $user = \App\User::where($this->username(), $username)->first();

            if (!$user) {

                $user = new \App\User();
                $user->username = $username;
                $user->password = '';

                $sync_attrs = $this->retrieveSyncAttributes($username);
                foreach ($sync_attrs as $field => $value) {
                    $user->$field = $value !== null ? $value : '';
                }
            }

            $this->guard()->login($user, true);
            return true;
        }
        // the user doesn't exist in the LDAP server or the password is wrong
        // log error
        return false;
    }

    protected function retrieveSyncAttributes($username)
    {
        $ldapuser = Adldap::search()->where(env('LDAP_USER_ATTRIBUTE'), '=', $username)->first();
        if (!$ldapuser) {
            // log error
            return false;
        }

        $ldapuser_attrs = null;

        $attrs = [];

        foreach (config('ldap_auth.sync_attributes') as $local_attr => $ldap_attr) {
            if ($local_attr == 'username') {
                continue;
            }

            $method = 'get' . $ldap_attr;
            if (method_exists($ldapuser, $method)) {
                $attrs[$local_attr] = $ldapuser->$method();
                continue;
            }

            if ($ldapuser_attrs === null) {
                $ldapuser_attrs = self::accessProtected($ldapuser, 'attributes');
            }

            if (!isset($ldapuser_attrs[$ldap_attr])) {
                // an exception could be thrown
                $attrs[$local_attr] = null;
                continue;
            }

            if (!is_array($ldapuser_attrs[$ldap_attr])) {
                $attrs[$local_attr] = $ldapuser_attrs[$ldap_attr];
            }

            if (count($ldapuser_attrs[$ldap_attr]) == 0) {
                // an exception could be thrown
                $attrs[$local_attr] = null;
                continue;
            }

            $attrs[$local_attr] = $ldapuser_attrs[$ldap_attr][0];
            //$attrs[$local_attr] = implode(',', $ldapuser_attrs[$ldap_attr]);
        }

        return $attrs;
    }

    protected static function accessProtected($obj, $prop)
    {
        $reflection = new \ReflectionClass($obj);
        $property = $reflection->getProperty($prop);
        $property->setAccessible(true);
        return $property->getValue($obj);
    }
}
shabeebrizvi commented 5 years ago

Finally, i figured out the issue... updated the code... hence issue closed!