Does Nginx support LDAP authentication?

nginx doesn't do LDAP: you have to use xsendfile with a 3rd party script you create to handle LDAP authentication

http://wiki.nginx.org/NginxXSendfile


There is an unofficial LDAP module for nginx : nginx-auth-ldap.


There is a 3rd party module nginx-auth-ldap that you can use. I have not tried it yet, but I may update my answer later.

using nginx X-accel

The documentation for X-accel just explains that a page may use a header to have nginx serve a file (rather than PHP or django or ruby or name-your-not-as-efficient-as-nginx-stack-here).

e.g. workflow:

  • user visits /download.php?path=/data/file1.txt,
  • download.php returns WWW-Authenticate + 401 Unauthorized,
  • user's browser shows the authentication form and retries,
  • user visits /download.php?path=/data/file1.txt but now nginx has the credentials,
  • nginx may pass $remote_user and $http_authorization to fastcgi script,
  • download.php does the authentication and decides whether to return 403 Forbidden or set the header X-Accel-Redirect header.

setting nginx internal location

While you can use X-Accel to serve static assets, the use case here is we want the requests to be authenticated, which is why we use internal.

location /protected/data/ {
    internal;
    alias /path/to/data/files/;
}

setting up the download script

Here we go:

location /download.php$ {
    fastcgi_pass  unix:/var/run/php-fpm/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME /scripts/download.php;
    fastcgi_param PHP_AUTH_USER $remote_user;
    fastcgi_param PHP_AUTH_PW $http_authorization;
    include fastcgi_params;
}

please note: the PHP script uses PHP_AUTH_USER and PHP_AUTH_PW, which is captured by nginx, so in order to use them in the PHP script, we need to give to provide them explicitly.

cooking up an ldap authentication in PHP

For my use case, I installed php-fpm and php-ldap on my system.

Here is a decent authenticate function:

function authenticate() {
    // I'm watching you.
    error_log("authreq: " . $_SERVER['REMOTE_ADDR']);
    // mark that we're seeing the login box.
    $_SESSION['AUTH'] = 1;
    // browser shows login box
    Header("WWW-Authenticate: Basic realm=LDAP credentials.");
    Header("HTTP/1.0 401 Unauthorized");
    die('Unauthorized.');
}

Here is a decent code path for forbidden access:

function forbidden() {
    error_log("forbidden: " . $_SERVER['REMOTE_ADDR'] . ', user: ' . $_SERVER['PHP_AUTH_USER']);
    // avoid brute force attacks
    sleep(rand(0, 3));
    // re-display login form
    session_destroy();
    // don't give too much info (e.g. user does not exist / password is wrong)
    Header("HTTP/1.0 403 Forbidden");
    // yes I did put the same message.
    die('Unauthorized.');
}

And for the meat of the LDAP authentication:

function ldap_auth() {
    $ldap_server = 'ldap://ldap.example.com/';
    $ldap_domain = 'dc=example,dc=com';
    $ldap_userbase = 'ou=Users,' . $ldap_domain;
    $ldap_user = 'uid=' . $_SERVER['PHP_AUTH_USER'] . ',' . $ldap_userbase;
    $ldap_pass = $_SERVER['PHP_AUTH_PW'];

    // connect to ldap server
    $ldapconn = ldap_connect($ldap_server)
        or die("Could not connect to LDAP server.");
    ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3) ;
    if ($ldapconn) {
        // try to bind/authenticate against ldap
        $ldapbind = @ldap_bind($ldapconn, $ldap_user, $ldap_pass) || forbidden();
        // "LDAP bind successful...";
        error_log("success: " . $_SERVER['REMOTE_ADDR'] . ', user: ' . $_SERVER['PHP_AUTH_USER']);
    }
    ldap_close($ldapconn);
}

Here you have the main body of the script which uses the request uri.

if (@$_SESSION['AUTH'] != 1) {
    authenticate();
}

if (empty($_SERVER['PHP_AUTH_USER'])) {
    authenticate();
}

// check credentials on each access
ldap_auth();

// Get requested file name
// you can use the query string or a parameter
// or the full request uri if you like.
$path = $_GET["path"];

error_log("serving: " . $_SERVER['REMOTE_ADDR'] . ', user: ' . $_SERVER['PHP_AUTH_USER'] . ', path: ' . $path);

header("Content-Type: ", true);
header("X-Accel-Redirect: /protected" . $path);

semi-transparent file browsing

I also published this as a gist:

location /protected/data/ {
    internal;
    autoindex on;
    alias /path/to/data/files/;
}

location /data/ {
    fastcgi_pass  unix:/var/run/php-fpm/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME /scripts/auth.php;
    fastcgi_param PHP_AUTH_USER $remote_user;
    fastcgi_param PHP_AUTH_PW $http_authorization;
    include fastcgi_params;
}

and pretty much the same PHP script except the body:

// Get requested file name
$path = $_SERVER["REQUEST_URI"];
error_log("serving: " . $_SERVER['REMOTE_ADDR'] . ', user: ' . $_SERVER['PHP_AUTH_USER'] . ', path: ' . $path);
header("Content-Type: ", true);
header("X-Accel-Redirect: /protected" . $path);