Conditional auth_basic_user_file depending on the request path

I would like to authorize users depending on the requested path. For example, only user1 and user2 should have access to /projects/1.

My /etc/nginx/.htpasswd looks like this:

user1:$hashed_psswd1
user2:$hashed_psswd2
user3:$hashed_psswd3
...

Here's the location block in nginx config:

location ~ /projects(/.*) {
    auth_basic "Please provide credentials";
    auth_basic_user_file /etc/nginx/.htpasswd;
    include /var/www/html/myapp.com/config/nginx/git-http-backend.conf;
} 

And the rails action:

def git
  redirect_to "#{Rails.configuration.x.domain}/projects/#{@project.id}"
end

The authentication seems to work:

$ git clone http://localhost:3000/projects/1
Cloning into '1'...
Username for 'http://localhost:3001': user1  
Password for 'http://deploy@localhost:3001': 
remote: Enumerating objects: 94, done.
# ... it successfully clones the git repo...

The problem is that user3 is able to clone project 1, but he shouldn't be able to clone such project.

So how could I authorize users in nginx + rails depending on the requested path?


Solution 1:

Here is a config I've just tested with OpenResty 1.17.8.2 (based on nginx 1.17.8 core) and can confirm that it is workable:

map $uri $realm {
    ~^/projects/    "Protected projects";
    ~^/images/      "Protected images";
    default         off;
}
map $uri $authfile {
    ~^/projects/    /path/to/.htpasswd_projects;
    ~^/images/      /path/to/.htpasswd_images;
}
server {
    ...
    auth_basic $realm;
    auth_basic_user_file $authfile;
    ...
}

.htpasswd_projects and .htpasswd_images files contains each own user/password list. With the above config every URI started with /projects/ prefix is protected with basic authorization and available only for users listed in the htpasswd_projects file, every URI started with /images/ is protected with basic authorization and available only for users listed in the htpasswd_images file, and rest of the URIs are available without authorization for everyone.

Update

Here is another example, using dynamically generated .htpasswd path depending on the requested URI:

location ~ ^/projects/(?<project_id>[^/]+) {
    if (!-f /var/www/projects/$project_id/.htpasswd) {
        # '.htpasswd' file for the requested project does not exists
        # assuming wrong project ID is given, returning HTTP 404 Not found
        return 404;
    }
    auth_basic "Please provide credentials for project $1";
    auth_basic_user_file /var/www/projects/$project_id/.htpasswd;
    ...
}

This one would work with the following .htpasswd files tree:

/var/www/projects
         ├── 1
         │   └── .htpasswd
         ├── 2
         │   └── .htpasswd
         ...

You can place this projects directory wherever you want, it isn't dependent on web server root.