Describe the feature you'd like:
According to https://ldaprecord.com/docs/laravel/v2/auth/#synchronized-database-authentication I should use 'Synchronized Database Authentication' if I want my site to be accessible even when my LDAP server is unavailable or down.
Unfortunately this doesn't work once you introduce the WindowsAuthenticate middleware into your application.
I'm not sure if I'm overlooking something conceptually on why this might be a bad idea, but I can't think of any reason why it would be. It seems like it might require a bit of work in the code to handle this, though. Possibly falling back when an LDAP connection can't be established to trust the database source for the user...
Context: I have an Active Directory which my application accesses by a flakey 'LDAP' connection for database synchronization. The SSO layer doesn't use LDAP, but instead integrates with ADFS which is not flakey. On occasion users of my site will fail to access the site because the LDAP connection was briefly down and the WindowsAuthenticate middleware relies on this.
As always, your thoughts are much appreciated.
Hi @aanderse,
Unfortunately this doesn't work once you introduce the
WindowsAuthenticatemiddleware into your application.
Why does this not work? Do you receive an exception when attempting to login a user through SSO and the LDAP connection isn't available?
Do you receive an exception when attempting to login a user through SSO and the LDAP connection isn't available?
Yes. I have included the stack trace in case you're interested.
Why does this not work?
After I received that error and briefly looked through the code I figured LDAP connectivity was a hard requirement... I'm going to take another look at the code.
Reading through the code I track it down to this:
WindowsAuthenticate::attempt calls WindowsAuthenticate::retrieveAuthenticatedUserWindowsAuthenticate::retrieveAuthenticatedUser calls WindowsAuthenticate::getUserFromRepositoryWindowsAuthenticate::getUserFromRepository calls LdapUserRepository::findByLdapUserRepository::findBy calls LdapUserRepository::queryLdapUserRepository::query calls LdapUserRepository::newModelQuery, which ultimately returns config('auth.providers.ldap.model') ... an instance of something like LdapRecord\Models\ActiveDirectory\User... not App\Models\UserDoes that help?
Ah okay I understand! Thanks for the thorough explanation and for providing the stack trace! Really appreciate it 馃檶 !
You're right -- we're not catching any exception here. I think there has to be a mechanism in place to allow catching and bypassing failure if needed.
Maybe something along the lines of:
WindowsAuthenticate::failSilently();
What are your thoughts?
It could also make sense to encapsulate this inside of the LdapUserRepository itself instead, so any query exception (regardless of what type of LDAP authentication mechanism was used) could have this "toggleable" safety feature.
I think that sounds like a good solution. Nice and easy to turn on for people who can ensure proper authentication outside of an LDAP connection :+1:
Okay I've added exception handling by default in the WindowsAuthenticate middleware so it performs identically during standard form based authentication. This means with this new release, exceptions will be caught and reported to the log (using Laravel's report($e) helper) if the LDAP query fails. You won't have to change anything once you update.
This new release will come with the introduction of an LdapRecord class, which will be used to handle various settings, such as this one, so developers may enable "failing loudly" (i.e. throwing an exception on failure) themselves:
use LdapRecord\Laravel\LdapRecord;
// Don't catch exceptions during authentication.
LdapRecord::failLoudly();
I've made other improvements to overall customizability as well, allowing you to register your own LDAP user repository, or LDAP user authenticator:
use LdapRecord\Laravel\LdapRecord;
LdapRecord::locateUsersUsing(\App\Ldap\LdapUserRepository::class);
LdapRecord::authenticateUsersUsing(\App\Ldap\LdapUserAuthenticator::class);
This new release will be out tonight/tomorrow, after I add the docs for these new features 馃帀
I really appreciate your thoughts and contributions!
Once the release is out, I will close this issue, so you will be notified.
Ok I've just released v2.3.0.
Run composer update and you're good to go!
Thanks for your work on this @stevebauman!
I'm having a little trouble understanding how to apply this to my scenario, though. Since my application is protected by SSO I know that if AUTH_USER is set the user is authorized to view the site. What I'm looking to do is have the WindowsAuthenticate middleware authenticate my user (ie. register my user with laravel), regardless if my LDAP server is available or not.
Specifically, I incorrectly assumed that:
AUTH_USER is set andusers database contains an entry where domain and username/email (whatever identifier I'm using) match AUTH_USER andLDAP server is unavailable then......WindowsAuthenticate would still authenticate the user by pulling their record from the users table and trusting that the web server has already authenticated the user.
I'm trying to understand where I have misunderstood the documentation. Any help is much appreciated.
Hey @aanderse!
I'm trying to understand where I have misunderstood the documentation. Any help is much appreciated.
This is my fault 馃槄 -- I didn't read your original post closely enough. I think we can definitely do this 馃挭
My thought is maybe using a callback if failing to locate a user through the LdapRecord DatabaseUserProvider.
Here's an example:
WindowsAuthenticate::fallback(function ($provider, $username, $domain) {
return $provider->eloquent()->retrieveByCredentials([
'username' => $username,
'domain' => $domain,
]);
})
What are your thoughts?
This would basically be called here:
$user = $this->getUserFromRepository($provider->getLdapUserRepository(), $username);
if (! $user) {
return static::$fallback ? static::$fallback() : null;
}
Which would automatically log the user into the application if found in the database.
@stevebauman no problem. That solution looks great. It would give me the opportunity to do some logging that the LDAP connection can't be established, potentially run some other logic, and as your example demonstrates return the user solely based off the database. This would make my application work 100% of the time, for existing users, even when LDAP is temporarily unavailable - which is exactly what I'm looking for :+1:
Thanks again for your consideration and work on this!
Happy to help @aanderse! I've just released this feature in v2.3.1.
You can use either a callback or class with an __invoke method:
Using a class with __invoke:
class FallbackUserResolver
{
public function __invoke($provider, $username, $domain)
{
// Resolve the user.
}
}
WindowsAuthenticate::fallback(FallbackUserResolver::class);
Using a closure:
WindowsAuthenticate::fallback(function ($provider, $username, $domain) {
// Resolve the user.
})
Thanks for your guidance with this feature! 馃帀
This works great! Again, thank you so much @stevebauman :bow:
Awesome @aanderse! Really glad you're up and running!