DirectoryTree / LdapRecord

A fully-featured LDAP framework.
https://ldaprecord.com
MIT License
507 stars 45 forks source link

LdapRecord + Fortify + Multidomain #214

Closed fcno closed 3 years ago

fcno commented 3 years ago

Hi Steve. I'm exploring / studying a feature in your library and I'm stuck now. Initially, would it be possible to join LdapRecord Multidomain + Fortify (I do not want to use jetstream, only fortity)? If not possible, indicated, or feasible with little effort, please close the issue. The road so far: With a single domain, I was able to make it work (LdapRecord + Fortify without jetstream), but with a multi-domain I am taking a beating. Maybe I'm doing something terribly wrong. I don't have a LoginController since, I left it to AuthServiceProvider. There I am with authentication as suggested for the single domain. If I switch to multi-domain authentication, do I have to create a LoginController? Wouldn't it be possible to implement this in AuthServiceProvider? The default guard I would like to be set at run time, when the user posts the authentication form (with the input domain), then the default guard would be Alpha or Bravo, equal to the value of the domain input. Maybe I'm confusing the concepts, but if you can give me a direction I appreciate it, but if you can't, I appreciate it anyway.

stevebauman commented 3 years ago

Hi @Babiute,

The current documentation for multi-domain authentication is currently outdated and assumes you're using laravel/ui. I'm still in the process of updating this.

However that being said, the premise is quite the same. You must create two (or more) authentication guards (each having their own separate LDAP provider), modify your login view and add a selectable dropdown of available domains. Then, attempt to authenticate the user underneath the domain they selected by choosing which guard maps to which domain:

Fortify::authenticateUsing(function ($request) {
    // Determine the guard name by the submitted domain.
   $guard = $request->domain == 'alpha' ? 'alpha' : 'bravo';

    // Authenticate the user underneath the selected guard.
    $validated = Auth::guard($guard)->validate([
        'mail' => $request->email,
        'password' => $request->password,
    ]);

    return $validate ? Auth::getLastAttempted() : null;
});

You will need to modify several more things than the above, because by default, Laravel Fortify, Jetstream and Sanctum all use web as their default guard. You will have to provide a mechanism for dynamically swapping the guard instance depending on the authenticated users guard. I have not yet came to an all encompassing solution on this, so unfortunately you will have to tread the waters here and find out what works best for your application.

Hope this helps!

fcno commented 3 years ago

Hey @stevebauman Not using laravel UI too. I'm using a custom view. I just create myself a LoginController to see if there was a way to handle it, but there was not. Thx for your replay. I will see what I can do with your suggestion.

fcno commented 3 years ago

Some advances, but stuck again. To get around the need to redefine a default guard other than the web, I did the following, despite knowing that it is a 'hack', but for now, it is enough for my tests.

 'guards' => [

        'web' => [
            'driver' => 'session',
            'provider' => 'alpha',
        ],

        'alpha' => [
            'driver' => 'session',
            'provider' => 'alpha',
        ],

        'bravo' => [
            'driver' => 'session',
            'provider' => 'bravo',
        ],

Regarding the code you proposed, I think you forgot to inform the guard and, therefore, the return was always null. I informed the guard and the authentication happened beautifully.

return $validate ? Auth::($guard)->getLastAttempted() : null;

An alternative to your proposal would be to use config (['auth.defaults.guard' => 'admin']) or Auth::shouldUse($guard), but I don't know their side effect other than the tests I've done so far.

Either way, your proposal and the variants I tested are able to authenticate correctly. However, somehow I am not able to identify the authenticated user. Wherever I use dd(Auth::user()) the returned value is always null. I think I'm missing something, but I still haven't found what it is.

Btw, the valiable name in the last line of the code is $validated and not $validate :D

stevebauman commented 3 years ago

Hi @Babiute, thanks for the sponsorship, I appreciate it 👍

You will need to specify the guard when retrieving the authenticated user via Auth::guard(‘alpha’)->user(). There is no way around this, unfortunately.

Using multiple guards means you always have to specify the guard in each operation, or use:

Auth::shouldUse(‘alpha’);

$user = Auth::user();

This should get you going!

fcno commented 3 years ago

Hey @stevebauman . That's what I can do to help you to keep this beautiful work. It's not much, but its what I can do for now.

I know you closed the topic but your sugestion did not worked.

This is the code:

        Fortify::authenticateUsing(function ($request) {
            // Determine the guard name by the submitted domain.
           $guard = $request->domain == 'alpha' ? 'alpha' : 'bravo';

            // Authenticate the user underneath the selected guard.
            $validated = Auth::guard($guard)->validate([
                'samaccountname' => $request->username,
                'password' => $request->password,   
            ]);

            // To be able to get the default guard outside.
            session(['domain' => $request->domain]);

            return $validated ? Auth::guard($guard)->getLastAttempted() : null;
        });

If I dd the last line Auth::guard($guard)->getLastAttempted(), I can get the result of the authentication, but the Auth::($guard)->user(); allwyas return null, no matter where I'm. And sure, if I tried with Auth::shouldUse(‘alpha’) too but the results are the same.

I've tried to debug it and I ended up in Illuminate\Auth\SessionGuard class and there, its strange, but $this->user there is null.

stevebauman commented 3 years ago

Hi @Babiute,

A couple things to check:

From what you've told me, the session doesn't appear to be persisting between requests. You will have to navigate through your application and troubleshoot why this is the case, by dumping and dying in various locations throughout the Laravel request lifecycle. I cannot navigate this for you, as I don't have the code of your application, so unfortunately this is something you will have to tackle.

fcno commented 3 years ago

Hi @stevebauman. This weekend will be with the family. Tuesday I am back and then I will try what you suggested. As soon as I finish here, I give feedback so that other people who go through the same situation can help themselves with everything we recorded here in this post.

stevebauman commented 3 years ago

Hi @Babiute, no worries! 👍 Let me know if you'd like to link up via Zoom on Tuesday to troubleshoot this together. It'd be a lot easier than messaging back and forth.

fcno commented 3 years ago

Hi @stevebauman. I appreciate your help and I will provide a step-by-step for anyone who wants to use LdapRecord authentication with Fortify, but without Jetstream which my case. At the end, you evaluate whether it is interesting or not to include it in your official documentation. The road so far: I divided the objective into smaller problems and with that I managed to isolate the problem well so that I could optimize your time helping me. At the moment, I can authenticate myself in the Alpha domain and in the Bravo domain, however, the Auth::user (or Auth::guard('$guard') too) is only found in the views, when authenticated using the default guard. I've tried other strategies, but at the moment I'm out of ideas. About the date / time, please tell me when you can, and I ll be available on the date / time you choose. So: The problem comes down to finding the reason why user data is being lost when not using the default guard. Thx for your help. I think I'm very lucky to have met someone like you. Success.

fcno commented 3 years ago

Hello @stevebauman .

It seems that I found the solution to the problem.

As authentication was taking place in both domains, as I said, the problem boiled down to understanding why it was not possible to identify the authenticated user, when authentication occurred without being through the default guard.

The problem ,as far as I can tell, is you need to tell fortify too which guard will be used, because fortify need to know it too as you can see bellow.

// config/fortify.php

'guard' => 'alpha' // changed from  'web'

So, I had to set it in Fortify :: authenticateUsing method to properly work with a non default guard.

Fortify::authenticateUsing(function ($request) {
    // Determine the guard name by the submitted domain.
   $guard = $request->domain == 'alpha' ? 'alpha' : 'bravo';

    config(['fortify.guard' => $guard]);

    // ...

});

With that, everything is now working perfectly. Any final guidance? For me, I think I'm still missing something, because I think Auth::shouldUse ($guard) would be enough, even, to inform fortify the default guard. But my knowledge of Laraval / Fortify / LdapRecord still does not allow me to say whether this is desired or unwanted behavior.

You are free to close this topic.

In the coming days, I will send you the documentation for you to evaluate whether or not to include it in your official documentation. How can I send it too you?

stevebauman commented 3 years ago

Hi @Babiute, apologies for the late response. I've been hired at a different company full time, so my time has been invested in getting up and running with them.

So, I had to set it in Fortify :: authenticateUsing method to properly work with a non default guard.

Fortify::authenticateUsing(function ($request) {
    // Determine the guard name by the submitted domain.
   $guard = $request->domain == 'alpha' ? 'alpha' : 'bravo';

    config(['fortify.guard' => $guard]);

    // ...

});

This will only work on the initial authentication request unfortunately (see my response below).

Any final guidance? For me, I think I'm still missing something, because I think Auth::shouldUse ($guard) would be enough, even, to inform fortify the default guard. But my knowledge of Laraval / Fortify / LdapRecord still does not allow me to say whether this is desired or unwanted behavior.

You will have to set the default authentication's guard during each request -- this prevents you from having to specify the guard whenever you're attempting to access the currently authenticated user as you typically would, for example:

// Access the logged in 'alpha' domain's user:
Auth::guard('alpha')->user();

Here's how you can do that:

// app/Providers/AuthServiceProvider.php

public function boot()
{
    $this->registerPolicies();

    foreach (config('auth.gaurds', []) as $guard => $config) {
        if (Auth::guard($guard)->check()) {
            Auth::shouldUse($guard);
        }
    }

    // ...
}

I believe if you set the fortify.guard option to null, it will use the default guard (which you're setting dynamically upon boot) that is being used by Laravel's Auth manager.

In the coming days, I will send you the documentation for you to evaluate whether or not to include it in your official documentation. How can I send it too you?

That'd be awesome if you could! You can simply send the content here, or you can submit a pull-request to the documentation repository on the relevant page:

https://github.com/DirectoryTree/LdapRecord-Docs/blob/master/laravel/auth/laravel-jetstream.md

Hope this helps! Let me know if you have any further questions 👍

fcno commented 3 years ago

Hi @stevebauman.

No need to apologize man and I wish you success with your new job. I will try your suggestions and then I will come back to inform you about the result and help with the documentation of this functionality (First I need to get to know github better). For now, I'm going to close the topic and later I will trigger you again.

Hugs and success.

stevebauman commented 3 years ago

Oh please no apology is necessary, it's totally okay @Babiute! ❤️

I hope I didn't come off rude or abrupt in any kind of way. I'm here to support you with using LdapRecord. I want you to feel comfortable asking anything here 😄

I was hoping to get to chat with you on Zoom when I originally suggested it, but the new position got a bit in the way of that.

Let me know if you encounter any issues with what I mentioned above and we can go from there. Thanks for your understanding, I really appreciate it!

stevebauman commented 3 years ago

Whoops! I misread your first sentence (notable the apology part) 😅

fcno commented 3 years ago

Hi @stevebauman . After winning other steps in the project, I went back to authentication. As I braked again, I came to seek a light. Sorry, for reopening the topic, but I think it is better than opening another one because we have a history of what has been discussed so far.

// app/Providers/AuthServiceProvider.php

public function boot()
{
    $this->registerPolicies();

    foreach (config('auth.gaurds', []) as $guard => $config) {
        if (Auth::guard($guard)->check()) {
            Auth::shouldUse($guard);
        }
    }

    // ...
}

I understand the function of the code you suggested, but it seems to me that Auth :: guard ($ guard) -> check () always returns false. I've tried hardcoded and it also didn't work.

I suppose it is because I do not have access to session variables at this point in the lifecycle (Laravel rules), as I do within the authentication callback.

If I do hardcode Auth::shouldUse('alpha'); in the place you suggested, the whole application can see the authenticated user, but as Auth::guard('xx')->check() is never 'true', that code is never executed, so the suggestion do not solve the problem (maybe I miss something).

Right now, im using the latest version of ldaprecord and fortify (I did an update today).

Note: 01) Yes, I noticed this typo, and the problem is not with it. config('auth.gaurds') 02) If you are still available, I will also be on the day and time you can for that help via zoom.

stevebauman commented 3 years ago

Hi @Babiute, apologies for the very late reply to your recent update.

I've tried this myself yesterday and multi-domain works without issue -- I think your issue is in your routes/web.php file, as you must specify the guards inside of the middleware declaration, for example:

Route::middleware('auth:web,alpha,bravo')->group(function () {
    // Protected application routes here.
});

I have added this into the multi-domain documentation:

https://ldaprecord.com/docs/laravel/v2/auth/multi-domain/#routes

The above code I mentioned in earlier replies is actually already in the Laravel core in the Authenticate middleware:

https://github.com/laravel/framework/blob/44dbb4b606d7a049fb3b95594b495567a7c29223/src/Illuminate/Auth/Middleware/Authenticate.php#L56-L69

Please let me know if you're still interested in connecting via Zoom -- I'm free anytime after 5 PM EST. Thanks! ❤️

stevebauman commented 3 years ago

Closing due to inactivity.