Nginx - basic http authentication on PHP-script

15,521

I believe your question might already be answered here, but I will try to describe what I think the problem is.

First of all, as an aside, you should consider putting all of your fastcgi parameters in a configurastion file accessible to nginx for ease of use (e.g. /etc/nginx/conf.d/fastcgi_params).

Second, depending on how you set up the location block for the auth vs the php section, you will likely need to instruct nginx on how to deal with php files a second time in the protected location, or make sure the auth_basic directives are in the same location block as the one you pasted above, for example (taken from the aforementioned post):

server {
  listen 80;
  server_name my-awesome-php.site;
  root /path/to/root;

  # Normal files (blank location is OK, just means serve from root)
  location / {  }  

  # PHP for normal stuff
  location ~ \.php$ {
    include fastcgi.conf;
    fastcgi_pass  127.0.0.1:9000;
  } 

  # The protected location
  location /protected {
    auth_basic "Give me codes.";
    auth_basic_user_file /path/to/.htpasswd;
    location ~ \.php$ {
      include fastcgi.conf;
      fastcgi_pass  127.0.0.1:9000;
    }
  }
}

On my personal installation of nginx I am using php-fpm and my php scripts are not limited to cgi-bin so my configuration is quite different but it might offer you some additional insights. I have basic authentication working as I imagine you are expecting it to although in the below example an entire vhost is under basic auth and not just a folder:

fastcgi_params

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Example for server/host based auth (irrelevant sections removed)

server {
        server_name dev.foo.com;

        error_log /app/www/dev.foo.com/logs/error.log error;

        root /app/www/dev.foo.com/htdocs;
        index index.php index.html;

        auth_basic "Secret Files";
        auth_basic_user_file /app/www/dev.foo.com/conf/htpasswd;

        location ~ \.php$ {
                include       /etc/nginx/fastcgi_params;
                fastcgi_index index.php;
                fastcgi_split_path_info ^(.+\.php)(.*)$;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_pass  unix:/var/run/foo.com.sock;
        }


        location ~ /\.ht {
                deny all;
        }
}

Example for location based auth (irrelevant sections removed)

server {
        server_name foo.com;

        error_log /app/www/foo.com/logs/error.log error;

        root /app/www/foo.com/htdocs;
        index index.php index.html;

        location /protected {            
            auth_basic "Secret Files";
            auth_basic_user_file /app/www/foo.com/conf/htpasswd;

            location ~ \.php$ {
                include       /etc/nginx/fastcgi_params;
                fastcgi_index index.php;
                fastcgi_split_path_info ^(.+\.php)(.*)$;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_pass  unix:/var/run/foo.com.sock;
            }
        }

        location ~ \.php$ {
            include       /etc/nginx/fastcgi_params;
            fastcgi_index index.php;
            fastcgi_split_path_info ^(.+\.php)(.*)$;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass  unix:/var/run/foo.com.sock;
        }

        location ~ /\.ht {
            deny all;
        }
}
Share:
15,521
halfbit
Author by

halfbit

(my about me is currently blank.)

Updated on September 18, 2022

Comments

  • halfbit
    halfbit almost 2 years

    I added a PHP-Script that serves as "cgi-bin",
    Configuration:

    location ~^/cgi-bin/.*\.(cgi|pl|py|rb) {
        gzip  off;
        fastcgi_pass  127.0.0.1:9000;
        fastcgi_index cgi-bin.php;
        fastcgi_param SCRIPT_FILENAME    /etc/nginx/cgi-bin.php;
        fastcgi_param SCRIPT_NAME        /cgi-bin/cgi-bin.php;
        fastcgi_param X_SCRIPT_FILENAME  /usr/lib/$fastcgi_script_name;
        fastcgi_param X_SCRIPT_NAME      $fastcgi_script_name;
        fastcgi_param QUERY_STRING       $query_string;
        fastcgi_param REQUEST_METHOD     $request_method;
        fastcgi_param CONTENT_TYPE       $content_type;
        fastcgi_param CONTENT_LENGTH     $content_length;
        fastcgi_param GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param SERVER_SOFTWARE    nginx;
        fastcgi_param REQUEST_URI        $request_uri;
        fastcgi_param DOCUMENT_URI       $document_uri;
        fastcgi_param DOCUMENT_ROOT      $document_root;
        fastcgi_param SERVER_PROTOCOL    $server_protocol;
        fastcgi_param REMOTE_ADDR        $remote_addr;
        fastcgi_param REMOTE_PORT        $remote_port;
        fastcgi_param SERVER_ADDR        $server_addr;
        fastcgi_param SERVER_PORT        $server_port;
        fastcgi_param SERVER_NAME        $server_name;
        fastcgi_param REMOTE_USER        $remote_user;
    }
    

    PHP-Script:

    <?php
    
    $descriptorspec = array(
       0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
       1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
       2 => array("pipe", "w")   // stderr is a file to write to
    );
    
    $newenv = $_SERVER;
    $newenv["SCRIPT_FILENAME"] = $_SERVER["X_SCRIPT_FILENAME"];
    $newenv["SCRIPT_NAME"] = $_SERVER["X_SCRIPT_NAME"];
    
    if (is_executable($_SERVER["X_SCRIPT_FILENAME"])) {
      $process = proc_open($_SERVER["X_SCRIPT_FILENAME"], $descriptorspec, $pipes, NULL, $newenv);
      if (is_resource($process)) {
        fclose($pipes[0]);
        $head = fgets($pipes[1]);
        while (strcmp($head, "\n")) {
          header($head);
          $head = fgets($pipes[1]);
        }
        fpassthru($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        $return_value = proc_close($process);
      }
      else {
        header("Status: 500 Internal Server Error");
        echo("Internal Server Error");
      }
    }
    else {
      header("Status: 404 Page Not Found");
      echo("Page Not Found");
    }
    ?>
    

    The problem with it thought is that I cannot add basic authentication.
    As soon as I enable it for location ~/cgi-bin it gives me a 404 error when I try to look it up.

    How can I solve this?

    I thought about restricting access to only my second server where I then add basic authentication over a proxy, but there must be a simpler solution.

    Sorry for the bad title, I couldn't think of a better one.

    Edit: My solution, thanks to WerkkreWs answer, looks like this in the end:

    cgi-bin.conf:

    location ~^/.*\.(cgi|pl|p<|rb) {
        [...]
    }
    

    vhost.conf:

    server {
        [...]
        location ~^/cgi-bin {
            auth_basic "Restricted";
            auth_basic_user_file htusers;
            include cgi-bin.conf;
        }
        [...]
    }
    

    This may be insecure since cgi-bin.conf could be accidentally included in the server-tag (and thus enabling every client to execute scripts in every location), but since I only use it once I will stick with this solution.

    • Admin
      Admin about 12 years
      Can you add a little clarification as to what you are trying to protect? If you use the auth basic documentation on a server or location it should work fine, is your wrapper script throwing the 404 or the server?
  • halfbit
    halfbit about 12 years
    Perfect, it works when I nest the locations! Also, I have one config that I simply include if I need PHP, that seemed the best way for me since I only have a couple of sites on that server :)
  • Sergio
    Sergio about 11 years
    Your "Example for server/host based auth" is the only working configuration i've found to password protect a whole site with fastcgi php. Thanks for sharing it, saved my day.