DirectoryTree / LdapRecord-Laravel

Multi-domain LDAP Authentication & Management for Laravel.
https://ldaprecord.com/docs/laravel/v3
MIT License
508 stars 54 forks source link

Won't sign in with SSO with a DC alias #140

Closed TheWhichDoctor closed 4 years ago

TheWhichDoctor commented 4 years ago

Hi Steve,

I'm having an issue with SSO for LdapRecord-Laravel while trying to migrate from Adldap2. The company I work for has an AD set up with an Alias on the domain we sign our computers sign into. As in the DC is "CompanyName" and the DC we have when we connect to the network is "CNNET" (initials of the company+net). Adldap2 managed to sign people in perfectly fine with this, however LdapRecord does not.

In a local copy of the application I can set $_SERVER['AUTH_USER'] to "CompanyName\Username" and it signs in (This is how I managed to migrate all existing functions from Adldap2 to LdapRecord). When I switch from $_SERVER['AUTH_USER'] = "CompanyName\Username" to $_SERVER['AUTH_USER'] = "CNNET\Username" it does not sign in and goes into a sign-in loop (this is what happens on the live server).

We keep getting a pop-up asking for the username and password to sign in over and over. I believe this is because of a custom middleware I built to replace "auth", which checks that the user has permissions before proceeding to the next step of the request:

public function handle($request, Closure $next) {
    $permissions = array_slice(func_get_args(), 2);
    if (\Auth::check()){
        $user = \Auth::user();
        dd($_SERVER['AUTH_USER']; //outputs the AUTH_USER as expected
    } else {
        $user = new \App\User();
    }
    if(!$user->hasPermission($permission)){
        //As a result, this is where the code goes (default is no permissions)
        //However instead of aborting with the 401 error we get thrown into a sign-in loop
        abort(401);
    }
    return $next($request);
}

I believe the middleware is outputting Auth::check() as false is because LdapRecord is failing the sign-in. If I replace my middleware with the default "auth" middleware then instead of a sign-in loop, the user is given the login route and can successfully sign in (defeating the point of SSO). Running php artisan ldap:test returns a successful connection so I know that the issue is not in connecting to the database

auth.php:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'ldap',
    ],
],
'providers' => [
    'users' => [
        'driver' => 'ldap',
        'model' => App\User::class,
    ],
    'ldap' => [
        'driver' => 'ldap',
        'model' => LdapRecord\Models\ActiveDirectory\User::class,
        'database' => [
            'model' => App\User::class,
            'sync_passwords' => false,
            'sync_attributes' => [
                App\Handlers\LdapAttributeHandler::class,
            ],
        ],
    ],
],

Environment:

stevebauman commented 4 years ago

Hi @TheWhichDoctor!

The company I work for has an AD set up with an Alias on the domain we sign our computers sign into. As in the DC is "CompanyName" and the DC we have when we connect to the network is "CNNET" (initials of the company+net).

Thanks for the detailed explanation -- I believe I know what's going on here.

Can you add the following into your App\Providers\AuthServiceProvider?

WindowsAuthenticate::bypassDomainVerification();

For example:

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        WindowsAuthenticate::bypassDomainVerification();
    }
}

Then, try authenticating again on a domain computer and let me know your results!

TheWhichDoctor commented 4 years ago

Hi Steve, Thanks for the incredibly fast response. This fixed the issue we were having and SSO works successfully.

I noticed this function in the docs and it came with this warning:

Important: This is a security issue if you use multi-domain authentication and disable this check. If you only connect to one domain inside your application, this is not a security issue. You have been warned.

The company do plan on having a separate AD in an office in another country, so this might be an issue down the line. Is there a link where we can read and inform ourselves on the security implications of this function? Do you think there is a way around this (maybe bypassDomainVerification per-connection)? Or will the only way to be "completely" secure be to host 2 versions of the site, one for each AD?

Thanks again for letting me know of a fix for now!

stevebauman commented 4 years ago

Glad to help @TheWhichDoctor!

Yes there's a full write up here on why this is required (which you may have already read):

https://ldaprecord.com/docs/laravel/auth/usage/#sso-domain-verification

Will these two AD instances be in the same forest? Or are the completely independent?

If they are independent, are you planning on having separate Laravel authentication guards for each one?

stevebauman commented 4 years ago

This excerpt in the documentation is the crux of this problem if you are planning on using multiple Laravel auth guards to connect to multiple domains, while utilizing Single-Sign-On:

Since there is the possibility of users having the same sAMAccountName on two separate domains, LdapRecord must verify that the user retrieved from your domain is in-fact the user who is connecting to your Laravel application via Single-Sign-On.

TheWhichDoctor commented 4 years ago

Hi Steve, Sorry for the long wait on an answer. The ADs will be 2 completely independent instances. I wasn't planning on having separate authentication guards but if it's needed to get what we want then I'll have to. Thank you for all the time you took to write your responses - they are really helping a lot (especially with how simple the explanations are)

stevebauman commented 4 years ago

No worries @TheWhichDoctor!

If you plan on authenticating against two separate domains, you must have two separate guard instances. It is the only way to authenticate against more than one domain, as they are completely separate LDAP connections.

Glad I could help! If you require anything further please feel free to drop by and create another issue.