Pass-through / SSO Setup
Middleware
To enable single-sign-on in your Laravel application, insert the included WindowsAuthenticate
middleware on your middleware stack inside your app/Http/Kernel.php
file:
protected $middlewareGroups = [
'web' => [
// ...
\LdapRecord\Laravel\Middleware\WindowsAuthenticate::class,
],
];
The
WindowsAuthenticate
middleware uses the rules you have configured inside yourconfig/auth.php
file. A user may successfully authenticate against your LDAP server when visiting your site, but depending on your rules, may not be imported or logged in.
Multi-Domain SSO
To be able to use multi-domain single-sign-on, your LDAP directory servers must first be joined in a trust.
Consider we have two domains: alpha.local and bravo.local.
If you have a web server that is joined to the alpha.local domain that is hosting your Laravel application, it must allow users to authenticate to the bravo.local domain.
Once you have a working trust defined between your domains, you must follow the steps of setting up multi-domain authentication. You may skip step 2, if you do not need a login page for your users.
After completing the above linked guide, you must instruct the WindowsAuthenticate
middleware to
utilize your LDAP authentication guards that you have configured in your config/auth.php
file
by calling the guards
method:
// app/Providers/AppServiceProvider.php
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::guards(['alpha', 'bravo']);
}
Or, if you prefer, you may define the WindowsAuthenticate
middleware as a named middleware inside
your app/Http/Kernel.php
, and insert the guard names in the definition of your routes:
// app/Http/Kernel.php
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.windows' => \LdapRecord\Laravel\Middleware\WindowsAuthenticate::class,
// ...
],
Then, utilize it inside your routes file:
Important: When guarding your routes that require authentication via the
auth
middleware, you must add both guard names into it as well.
// routes/web.php
Route::middleware([
'auth.windows:alpha,bravo',
'auth:alpha,bravo',
])->group(function () {
// ...
});
Important:
The actual order of the middleware definition is critical here, so your users that are accessing your site through single-sign-on are logged into your application, prior to hitting theauth
middleware, which validates that they are in-fact logged in.
Otherwise, they will be simply redirected to your login page.
SSO Domain Verification
To prevent security issues using multiple-domain authentication using the WindowsAuthenticate
middleware, domain verification will be performed on the authenticating user.
This verification checks if the user's domain name is contained inside their full distinguished name, which is retrieved from each of your configured LDAP guards.
Only 'Domain Components' are checked in the user's distinguished name. More on this below.
To describe this issue in further detail -- the WindowsAuthenticate
middleware retrieves all of your configured
authentication guards inside your config/auth.php
file. It then determines which one is using the ldap
driver, and attempts to locate the authenticating users from each connection.
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.
For example, if a user visits your Laravel application with the username of:
ACME\sbauman
And LdapRecord locates a user with the distinguished name of:
cn=sbauman,ou=users,dc=local,dc=com
They will be denied authentication. This is because the authenticating user has a domain of
ACME
, but it is not contained inside their distinguished name domain components (dc
).
Using the same example, if the located user's distinguished name is:
cn=sbauman,ou=users,dc=acme,dc=com
Then they will be allowed to authenticate, as their ACME
domain exists inside
their distinguished name domain components (dc=acme
). Comparison against
each domain component will be performed in a case-insensitive manor.
If you would like to disable this check, you must call the static method bypassDomainVerification
on the WindowsAuthenticate
middleware inside your AppServiceProvider
:
Important: This is a security issue if you use multi-domain authentication, since users who have the same
sAMAccountName
could sign in as each other. You have been warned. If however, you connect to only one domain inside your application, there is no security issue, and you may disable this check as shown below.
// app/Providers/AppServiceProvider.php
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::bypassDomainVerification();
}
Swapping the Domain Extractor
If you would like to override the default mechanism that extracts the domain
from the user's account that is retrieved from the PHP request key, call the
WindowsAuthenticate::extractDomainUsing()
method and supply a callback.
The callback should return a string, or an array with two values. The first being the user's username, the second being the user's domain.
The returned value(s) will be passed into the Domain Validator, for validation.
The first (and only) argument of the closure will be equal to the
retrieved value from the configured PHP $_SERVER
key (default is AUTH_USER
).
WindowsAuthenticate::extractDomainUsing(function ($account) {
[$username, $domain] = array_pad(
array_reverse(explode('\\', $account)),
2,
null
);
return [$username, $domain];
});
To bypass extraction, supply a closure and return the account's value:
WindowsAuthenticate::extractDomainUsing(function ($account) {
return $account;
});
Swapping the Domain Validator
If you'd like to validate the user's domain in your own way,
call the WindowsAuthenticate::validateDomainUsing()
method, and supply either a closure, or a class.
The first argument will be the user's LdapRecord model, the second will be the user's username and the third argument will be the user's domain (extracted with the above Domain Extractor).
Return true
/false
whether the user has passed validation.
Using a Closure
Register the closure into the middleware:
use LdapRecord\Models\Model;
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
WindowsAuthenticate::validateDomainUsing(function (Model $user, $username, $domain = null) {
// Validate the user's domain.
});
Using a Class
Create the class with an __invoke()
method:
use LdapRecord\Models\Model;
class DomainValidator
{
/**
* Determine if the user passes domain validation.
*
* @param Model $user
* @param string $username
* @param string|null $domain
*
* @return bool
*/
public function __invoke(Model $user, $username, $domain = null)
{
// Validate the user's domain.
}
}
Register the class into the middleware:
WindowsAuthenticate::validateDomainUsing(DomainValidator::class);
Changing the Server Key
By default, the WindowsAuthenticate
middleware uses the AUTH_USER
key inside PHP's $_SERVER
array ($_SERVER['AUTH_USER']
). If you would like to change this, call the serverKey
method on
the WindowsAuthenticate
middleware inside your AppServiceProvider
:
// app/Providers/AppServiceProvider.php
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::serverKey('PHP_AUTH_USER');
}
Remember Single-Sign-On users
As of LdapRecord-Laravel version v1.9.0
, users signed in to your application via the
WindowsAuthenticate
middleware will no longer be automatically "remembered".
This shouldn't have any effect on your application, but if you need to re-enable
this feature, you must call the rememberAuthenticatedUsers
method on the
WindowsAuthenticate
middleware inside your AppServiceProvider
:
// app/Providers/AppServiceProvider.php
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::rememberAuthenticatedUsers();
}
Selective / Bypassing Single-Sign-On
Occasionally you may need to allow users who are not a part of the domain to log in to your application, as well as allowing domain users to automatically sign in via Single-Sign-On.
Depending on your web servers operating system, this process can be different.
Linux (HTTPD)
If you're using the Apache httpd
server with plugins enabling the sharing of a domain joined user's
username via the REMOTE_USER
server variable, you must update the WindowsAuthenticate
middleware
to use this variable, instead of the default AUTH_USER
.
To do this, call the WindowsAuthenticate::serverKey()
method in your AppServiceProvider::boot()
method:
// app/Providers/AppServiceProvider.php
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::serverKey('REMOTE_USER');
}
If a user is not on a domain joined computer, then the REMOTE_USER
variable will
be null
and the WindowsAuthenticate
middleware will be automatically bypassed,
allowing regular web application users to sign in.
Windows (IIS)
A Windows hosted application with NTLM / Windows authentication enabled is unfortunately all-or-nothing on your entire web application instance. This means, you cannot enable a single HTTP endpoint in your application to use Single-Sign-On or exempt a portion of your application. However, there is a workaround that is used frequently in the industry.
The goal is to have two URLs that point to the same Laravel application. One has Windows authentication
enabled, and another does not. This is typically identified by an sso
subdomain:
<!-- Standard URL -->
my-app.com
<!-- Single-Sign-On URL -->
sso.my-app.com
To do this, you must create a new IIS application instance and point to the same Laravel application. Then, you simply have Windows authentication enabled on one instance, and left disabled on another.
Nothing needs to be done in your Laravel application. The WindowsAuthenticate
middleware
will only attempt to authenticate users when the AUTH_USER
server key is present,
so it can remain in the global middleware stack.
Forcing logouts on non Single-Sign-On users
If a user successfully authenticates to your Laravel application through single-sign-on, and their LDAP account happens to be deleted or disabled, the user will remain authenticated to your application for the duration of your Laravel application's session.
If you would like all users in your application to be signed out automatically
if SSO credentials are not available from your web server, call the
logoutUnauthenticatedUsers
method on the WindowsAuthenticate
middleware in your AppServiceProvider::boot()
method:
Important: Only enable this feature if Single-Sign-On is the only way you authenticate users. If a non-Single-Sign-On user has a session open, it will be ended automatically on their next request.
// app/Providers/AppServiceProvider.php
use LdapRecord\Laravel\Middleware\WindowsAuthenticate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
WindowsAuthenticate::logoutUnauthenticatedUsers();
}