What is the best way to use Bearer token authentication along with Proxy Digest Authentication? Is it even possible?

953 Views Asked by At

We have few Web APIs accessible via Bearer token authentication flow. Now, we need to support these APIs with Proxy based authentication as well (e.g., Basic, Digest, NTLM). So far, I was able to test Bearer token authentication along with Basic authentication, now I am exploring Digest authentication.

Since Bearer token flow uses Authorization header, while Proxy Digest Authentication also has overlapping/conflicting usage of Authorization header in authentication flow between client and proxy server. Can we achieve both authentication schemes (Bearer and Proxy Digest) simultaneously?

On the client side, I have used okhttp-digest (https://github.com/rburgst/okhttp-digest) library in Java. While for testing the Digest auth scheme on proxy server, I have tried Squid server and Nginx/OpenResty server with digest_auth module (https://www.nginx.com/resources/wiki/modules/auth_digest/), but no luck in any of the proxy servers. In case of Squid server, it replaces the custom Host header sent from client side with the current URL's host (not desirable from the Web APIs side). While in case of Nginx/OpenResty, it sends 401 to which okhttp-digest is not compatible in case of proxy authentication.

Is there any way in particular any modules in OpenResty/Nginx which could help me achieve both kind of authentication from client/proxy server side?

Currently, I am looking into how I can send the Bearer token on successful authentication at client side in subsequent request, though I am not sure if it would work or again have conflict at proxy server side.

Client side code using okhttp-digest library.

import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.DispatchingAuthenticator;
import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {

    public static String bearerToken = "Bearer abc";

    public static void main(String[] args) throws Exception {

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.0.3", 80));

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();

        Credentials credentials = new Credentials("username", "password");
        final BasicAuthenticator basicAuthenticator = new BasicAuthenticator(credentials);
        final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);

        DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
                .with("digest", digestAuthenticator)
                .build();

        final OkHttpClient client = builder
                .proxy(proxy)
                .proxyAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
                .addInterceptor(new AuthenticationCacheInterceptor(authCache))
                .addInterceptor(logging)
                .build();

        String url = "http://www.example.com";

        Request request = new Request.Builder()
                .url(url)
                .method("GET", null)
                .addHeader("Host", "admin.example.com")
                .addHeader("Authorization", bearerToken)
                .get()
                .build();
        Response response = client.newCall(request).execute();
        System.out.println(response.body().string());
    }
}

Nginx.conf inside OpenResty (built with auth_digest and other required modules).

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    resolver  114.114.114.114;
#    resolver 8.8.8.8;

log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time req_header:"$req_header" req_body:"$request_body" '
'resp_header:"$resp_header" resp_body:"$resp_body"';

lua_need_request_body on;

body_filter_by_lua '
  local resp_body = string.sub(ngx.arg[1], 1, 1000)
  ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
  if ngx.arg[2] then
     ngx.var.resp_body = ngx.ctx.buffered
  end
';

header_filter_by_lua ' 
  local h = ngx.req.get_headers()
  for k, v in pairs(h) do
      ngx.var.req_header = ngx.var.req_header .. k.."="..v.." "
  end
  local rh = ngx.resp.get_headers()
  for k, v in pairs(rh) do
      ngx.var.resp_header = ngx.var.resp_header .. k.."="..v.." "
  end
';
    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        set $resp_body "";
        set $req_header "";
        set $resp_header "";

        auth_digest_user_file /etc/proxy/users_digest;
        auth_digest 'realm';
        proxy_connect;
        proxy_connect_allow            443;
        proxy_connect_connect_timeout  10s;
        proxy_connect_read_timeout     10s;
        proxy_connect_send_timeout     10s;
        
        location / {
        add_header X-DEBUG-MSG-1 "$uri";
        add_header X-DEBUG-MSG-2 "$http_authorization";
        proxy_set_header Host $host;
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Host;
        proxy_pass_header Authorization;
            proxy_pass $scheme://$host$request_uri;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       443 ssl;
        server_name  localhost;

        set $resp_body "";
        set $req_header "";
        set $resp_header "";

        auth_digest_user_file /etc/proxy/users_digest;
        auth_digest 'realm';
        proxy_connect;
        proxy_connect_allow            443;
        proxy_connect_connect_timeout  10s;
        proxy_connect_read_timeout     10s;
        proxy_connect_send_timeout     10s;

        ssl_certificate      /etc/ssl/certs/nginx-selfsigned.crt;
        ssl_certificate_key  /etc/ssl/private/nginx-selfsigned.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            add_header X-DEBUG-MSG-1 "$uri";
            add_header X-DEBUG-MSG-2 "$http_authorization";
            proxy_set_header Host $host;
            proxy_set_header Authorization $http_authorization;
            proxy_pass_header Host;
            proxy_pass_header Authorization;
            proxy_pass $scheme://$host$request_uri;
        }
    }
}

P.S. I think I cannot use Nginx with digest_auth module as it always returns 401 (with WWW-Authenticate response header instead of Proxy-Authenticate response header) as it expects digest auth info in Authorization request header instead of Proxy-Authorization request header. So, for testing the Digest auth scheme, what I need is a forward proxy server (something like Squid but not Squid itself as it messes up with Host request header which is not expected in my use-case) which does Proxy Authentication rather than Server Authentication.

0

There are 0 best solutions below