Add Authentication to Shiny Server with Nginx

Shiny Server is a great tool, but I’ve always found it odd that there was no built-in password authentication. Sure, the Shiny Pro edition has SSL auth., but even for open source projects, I’m not really crazy about just anyone hitting my server whenever they want.

To solve this little problem, I whipped up two work-arounds. One solution uses an Nginx server with basic authentication and the second uses Nginx with SSL auth. The examples below are based on a fresh install of Ubuntu 14.04. A “quick start” version of the exact environment I used can be had here.

Deploy Shiny Server with Nginx Basic Authorization

The trick is to have Shiny only serve to the localhost and have Nginx listen to localhost and only serve to users with a password. This is fairly straight forward and involves editing the Nginx default.conf as well as the Shiny Server conf.

First, make sure you’ve got Nginx installed.

sudo apt-get install nginx

Nginx uses ufw firewall on Ubuntu, so you’ll have to start ufw and enable the correct ports.

sudo ufw enable

sudo allow 'Nginx Full'

Also, make sure you’ve got Apache2-utils, you’ll use this to store the usernames and passwords.

sudo apt-get install apache2-utils

Before you go on, shut down both Shiny and Nginx

sudo service nginx stop
sudo stop shiny-server

Next, you’ll need to edit the Nginx default.conf file.

sudo nano /etc/nginx/sites-available/default

Copy and paste the following into your default.conf

server {
    listen 80; 
    
    location / {
      proxy_pass http://127.0.0.1:3838/;
      proxy_redirect http://127.0.0.1:3838/ $scheme://$host/;
      auth_basic "Username and Password are required"; 
      auth_basic_user_file /etc/nginx/.htpasswd;
    }
  }

Once that’s done, you’ll need to edit Shiny Server’s conf file so it only serves to loaclhost. Otherwise users would be able to creep around your authentication by going to port 3838.

sudo nano /etc/shiny-server/shiny-server.conf

Copy and paste the below to your shiny-server.conf.

server{
    listen 3838 127.0.0.1;
    
    location / {
    site_dir /srv/shiny-server;
    log_dir /var/log/shiny-server;
    directory_index on;
    }
}

Now it’s time to create some usernames and passwords.

cd /etc/nginx
sudo htpasswd -c /etc/nginx/.htpasswd exampleuser

Restart Nginx and Shiny

sudo service nginx start
sudo start shiny-server

Ta-da, now you’ve got a password protected Shiny Server! Note, Shiny is now served by port 80 instead of port 3838!
loginnginx

Deploy Shiny Server with Nginx SSL Authorization

This is basically the same as above, but we’re going to direct the reverse-proxy to port 443 with SSL instead of port 80. The only “gotcha” is we’ll need a signed SSL certificate to view the page. There’s two ways to go about this: use a self-signed certificate with IP addresses or to use a trusted certificate with a domain name. Since this is just testing, I’ll use the self-signed method. If you need a trusted certificate, there’s a good tutorial on using letsencrypt to get a free trusted cert.

First we have to create a self-signed certificate. This is going to live in the nginx folder for ease of use.

cd /etc/nginx
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/server.key -out /etc/nginx/server.crt

Now use the same nginx default.conf method as above but add lines to read the SSL cert.

# Redirect all traffic from port 80 to SSL port
server {
    listen 80;
    return 301 https://$host$request_uri;
}
# Set reverse proxy to port 443
server {
    listen 443 ssl;
        ssl on;
        ssl_certificate /etc/nginx/server.crt;
        ssl_certificate_key /etc/nginx/server.key;
    
    location / {    
        proxy_pass http://127.0.0.1:3838;
        proxy_redirect http://127.0.0.1:3838/ https://$host/;
        auth_basic "Username and Password are required"; 
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

The changes to shiny-server.conf are the same as above.

server{
    listen 3838 127.0.0.1;
    
    location / {
    site_dir /srv/shiny-server;
    log_dir /var/log/shiny-server;
    directory_index on;
    }
}

If everything is working correctly, you should be staring at an ugly error message in your browser telling you that this is an “unsafe website.” This is due to the self-signed certificate. Just ignore that, add an exception and you should be confronted with a login box.
sslauthnginx

This is purely for testing purposes. This hasn’t been fully tested so don’t go putting it into production. If you really want to take things a step further, I would look into getting a trusted cert with letsencrypt, so you won’t have to deal with the ugly error page.

One more thing, the above is a VERY basic Nginx setup, the full-monty for the Nginx conf file would probably look something like this:

# Redirect all traffic from port 80 to SSL port
server {
    listen 80;
    return 301 https://$host$request_uri;
}
# Set reverse proxy to port 443
server {
    listen 443 ssl;
        ssl on;
        ssl_certificate /etc/nginx/server.crt;
        ssl_certificate_key /etc/nginx/server.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    
    location / { 
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        proxy_pass http://127.0.0.1:3838;
        proxy_redirect http://127.0.0.1:3838/ https://$host/;
        auth_basic "Username and Password are required"; 
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

 

23 thoughts on “Add Authentication to Shiny Server with Nginx

    1. You could put the .htpasswd file in the shiny-server folder, but Nginx would still have to read it. As far as I know, you only true Shiny server-side password is built into Shiny Pro edition.

  1. Thanks for this write-up, Kris.

    Any thoughts on how to restrict access to a subset of directories in /srv/shiny-server/? For example, because shiny-server will walk the directory tree in shiny-server we can create open/ and closed/ directories; I’d like to leave the apps in ‘open/’ available without login and the apps in ‘closed/’ locked down. (In this case, I want restricted access for apps in development…)

    It seems like nginx can handle this; I can get the authentication window to spawn in the right place (i.e., at http:///shiny/closed/), and it looks like the authentication works correctly…not a Forbidden Access error, but a 500 Internal Server Error. This makes me think it’s an issue with my shiny-server.conf, but the correct configuration is unclear…

    Thanks for any ideas you have!

    1. Hmmmm… Could you just make a chmod rule on the closed folder to only allow access to certain users?

      1. Thanks for the suggestion, Kris. I think I figured it out without chmod-ing. I have a directory structure like this:

        /srv/shiny-server/
        |
        – open
        | |
        | – app1
        | |
        | – …
        |
        – closed
        |
        – app2
        |
        – …

        And using these blocks in the nginx config (not-yet-SSL, will do that tomorrow):

        server {
        listen 80 default_server;
        # listen [::]:80 default_server ipv6only=on;

        # root /usr/share/nginx/html;
        # index index.html index.htm;

        # Make site accessible from http://localhost/
        server_name localhost;

        # lock down the closed section of the site (restricted apps)

        location ^~ /shiny/closed/ {
        proxy_pass http://127.0.0.1:3838/closed/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection “upgrade”;
        auth_basic “Username and password are required”;
        auth_basic_user_file /etc/nginx/.htpasswd;
        rewrite ^(/shiny/closed/[^/]+)$ $1/ permanent;
        }

        location /shiny/ {
        proxy_pass http://127.0.0.1:3838/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection “upgrade”;
        rewrite ^(/shiny/[^/]+)$ $1/ permanent;
        }


        }

        This configuration at least appears to be working…I can’t get to the apps in closed/ without logging in, but can access those in open/. If I find a problem with this configuration then I will post back here.

        Thanks again!

        1. Sorry about the formatting above…I didn’t try markdown or html tags, though I don’t know if either would have worked.

  2. I tried to do the “basic” nginx configuration that you have shown above, but shiny only shows the input interface and not the output. You can test it here: http://188.166.213.255/sample-apps/hello/ This is the sample app for the shiny server. It should show a histogram, but adding the “basic” nginx somehow disable the output.

    1. I can’t replicate or get past the password box. However, the Nginx piece seems to be working. If you can’t see the histogram or rmardown box, I’d guess the user “shiny” doesn’t have permissions to access your R package library. By default, Shiny will run as user “shiny.” Open R in your terminal and find your package library by typing `.libPaths()` then make sure user ‘shiny’ has permissions to read from that location.

      1. I’m really sorry for not providing you with the login info. I terminated the server and rebuilt a new one from scratch. The new server is at: http://128.199.206.186/sample-apps/hello/ username is “shiny”, password is “shiny”. The issue still existed after a clean installation, chmod -R 777 to all the folder in .libPaths(), chown -R shiny to all the folder in .libPaths(). As soon as I turn off nginx service, the server works fine.

        1. What version of Ubuntu are you using out of curiosity?

          I was able to replicate the problem using the new version of Shiny on Ubuntu 16.04. I’m going to back-track and try installing the old version. It’s very possible that the folks at Rstudio made a change in Shiny Server to dissuade homebrew authentication.

        2. So, here’s something interesting. I got this method working on the old version of Shiny but not on the new one.

          I’ve got this shell script I use to spin-up Shiny test boxes. The script installs the old version of Shiny. Tested and varafied on Ubuntu 14.04, however the new version of Shiny produced the same results you’re getting.

          The script is on my GitHub page.

          I’ll try to figure out the new version of Shiny but until then, I would consider this method deprecated.

          1. Thank you for your help and prompt reply! Hope you can crack the new version of Shiny.

    1. Nice, I followed the guide in your link and the problem (shiny did not render the plot) has been resolved. Thank you very much.

  3. How do you logout? Can multiple people use the same login at once?

    I’ve been able to get it to work on my computer, but when I go to an app from my ipad, I have the same problem (no output, only UI).

    1. There’s no logout because you’re not really “logging in” to anything, just a password protection to access the web-server. Multiple users should be able to use it at the same time, but I haven’t tested that. As I said in the article though, this is just a proof-of-concept. Shiny sells multi-user auth in their Pro version. I wouldn’t recommend using something like this in production.

Leave a Reply