NGINX reverse-proxy with optional SSL client authentication

authenticationnginxreverse-proxyssl

We have an NGINX proxy which is supposed to be multiplexing an application at https://server/app/ and an API located at https://server/. Additional difficulty is to protect the API via SSL client authentication.

This is my attempt of an NGINX server configuration:

server {
    listen       443 ssl;
    server_name  server;

    ssl_certificate     /etc/nginx/certs/site.crt;
    ssl_certificate_key /etc/nginx/certs/site.key;

    ssl_client_certificate /etc/nginx/certs/rootCA.pem;
    ssl_verify_client optional;

    location / {
        if ($request_method = GET) {
            return 301 https://$host/app/;
        }

        if ($ssl_client_verify != SUCCESS) {
            return 403;
        }

        limit_except GET {
            allow <SOME_IP>;
            deny  all;
        }

        add_header X-Client-Authentication "$ssl_client_verify";

        proxy_pass http://backend:8080/;
    }

    location /app {
        ...
    }
}

Unfortunately, even unauthenticated clients are forwarded to the backend. How can this be? The if($ssl_client_verify != SUCCESS) line should be handling this case.
I added the content of $ssl_client_verify to a reply header and it is NONE as expected. So clearly, the if statement with the return 403 is not being evaluated.

Btw. it is not possible for me to move the API to a different port or path

Best Answer

using if directive in location block is something that needs to be avoided. Because nginx configuration using declarative language. So if you are using if directive in location block and the first if directive condition met, then the other if directive is not executed. so my suggest is change if directive from location block to server block. here is the documentation and article if you want to learn more:

Update : you can do something like this. I have not tested it myself so i think it will have a syntax error. But, I think you got the idea.

server{
    ...

    #for location /
    set $Location_VerifySSL 'root';

    #for location /app/
    if ($request_uri ~* '^(/app)){
        set $Location_VerifySSL 'app';
    }

    set $Location_VerifySSL "$Location_VerifySSL_success";
    if ($ssl_client_verify != 'SUCCESS'){
        set $Location_VerifySSL "$Location_VerifySSL_fail";
    }

    if ($Location_VerifySSL = "root_fail") {
        return 403;
    }
    if ($Location_VerifySSL = "app_success") {
        return 200 'This is when url match ^(/app) and ';
    }
    ...

    location /{
        ...
    }
    ...
}