Recently the Datto SaaS Protection SRE team was met with a challenge to add authentication onto an open source web application that didn’t have a strong authentication story with it. We knew that we didn’t want to write an entire authentication layer just for this one application as the return on that time investment would be rather low. Instead we looked for a solution that would be easy to implement, easy to automate, and easy to understand months down the road long after the shine had worn off and was just another application we managed. Huge bonus points if the solution fit well within Datto’s overall authentication strategy, which for us meant integration with Okta.
Basic webapp with reverse proxy
The header_parrot app
To help illustrate how this solution works, I have created a small Go application that will parrot back any HTTP request it receives, including the headers. This will help out as well to validate once we start incorporating Okta and Vouch-Proxy, an SSO solution for Nginx that leverages the Nginx auth_request module, to make sure that Nginx is passing through what we expect. The code for header_parrot can be seen below:
package main
import (
"fmt"
"net/http"
"net/http/httputil"
)
func helloHeaders(w http.ResponseWriter, r *http.Request) {
requestDump, err := httputil.DumpRequest(r, true)
if err != nil {
fmt.Println(err)
}
fmt.Fprintf(w, string(requestDump))
}
func main() {
http.HandleFunc("/", helloHeaders)
http.ListenAndServe(":8080", nil)
}
The initial nginx reverse proxy configuration
Similarly, the initial starting point for Nginx is very basic. One thing to note here is that the examples in this post will not include SSL in the configuration, but you should be doing this with an SSL enabled configuration for your production deployments.
This example is running Ubuntu 20.04 and installing the OS provided nginx meta package. As such, we are creating the file /etc/nginx/sites-avilable/blog with the following contents:
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
proxy_pass http://localhost:8080;
include /etc/nginx/proxy_params;
}
}
Starting the header_parrot application and linking the above file into /etc/nginx/sites-enabled/ and restarting nginx will give us a nice starting point. When I hit the webserver I see the following:

Adding authentication with Okta and Vouch-Proxy
Creating the Okta application
The first step for adding authentication to header_parrot is to create the Okta application. In your Okta dashboard, you can click on the “Add Application” button, and you will be presented with a screen that looks like this:

Select “Web” and click next.
Fill in the fields similar to shown in the following picture and click “Done”

For name, it can be whatever you want to name your application. I have chosen Header Parrot. For the Login redirect URIs this should be the base URL of your application suffixed with /auth. This ties into the vouch-proxy setup which we will talk about next. For the Group assignments, I have chosen a group called SRE, but this would be whatever group you want to assign to the application once it is configured. Assigning users and groups can also be done after the application is created.
When you hit Done, you will be taken to the application page for the new application. On this page you will want to make a note of the following:
- Under client credentials, the Client ID and Client Secret.
- Under General Settings, the Okta domain (this should be your organization okta domain).
From here we can start the vouch proxy configuration.
Creating the Vouch-Proxy configuration and starting Vouch-Proxy
For this example, I will just be using git to clone the vouch-proxy git repository local to my web server. The following commands were ran to fetch and build vouch-proxy after ensuring the Golang was installed on my web server:
git clone https://github.com/vouch/vouch-proxy.git
cd vouch-proxy
./do.sh goget # (to fetch external golang dependencies)
./do.sh gobuildstatic
Once these commands are complete you should have a vouch-proxy binary in your current working directory.
Next we need to configure vouch proxy. By default the vouch-proxy binary will look for config/config.yml on startup. There are also plenty of command line options to pass in configuration options as well, but in this example we will be using the config file option.
Below is our basic configuration file for vouch-proxy:
vouch:
logLevel: debug
allowAllUsers: true
cookie:
name: VouchCookie
secure: false
domain: example.com
httpOnly: true
headers:
jwt: X-Vouch-Token
user: X-Vouch-User
querystring: access_token
redirect: X-Vouch-Requested-URI
session:
name: VouchSession
jwt:
maxAge: 240
compress: true
listen: 127.0.0.1
port: 9090
oauth:
provider: oidc
client_id: <redacted>
client_secret: <redacted>
auth_url: https://<redacted>.okta.com/oauth2/v1/authorize
token_url: https://<redacted>.okta.com/oauth2/v1/token
user_info_url: https://<redacted>.okta.com/oauth2/v1/userinfo
scopes:
- openid
- email
callback_url: http://nginx-okta-vouch.example.com/auth</redacted></redacted></redacted></redacted></redacted>
Here are some specific options details to help you as you build your vouch-proxy config.yml:
vouch.allowAllUsers: This setting does not allow any user to authenticate. This only allows all users who have been granted access to the application in Okta to authenticate. The other option is to add allow lists for specific users. This will be dictated by your Okta configuration and how you assign users to applications. For this example, we will allow anyone assigned to the application permission to log in.
vouch.cookie.secure: In this example, this setting is set to false. This is because we are not using SSL. In a real environment where you are using SSL this would need to be set to true.
oauth.client_id/oauth.client_secret: These values come straight from the Okta application config on the General tab.
oauth.auth_url/oauth.token_url/oauth.user_info_url: The redacted part of the domain should be filled in by your Okta domain (the one you log into Okta with).
oauth.callback_url: This url must match one of the Login redirect URIs listed on your Okta application under General Settings. If this url is not listed there your application will not be allowed to authenticate.
Once you have your config/config.yml created, you can start vouch proxy:
./vouch-proxy
Adding Vouch-Proxy to the basic webapp to authenticate users
Now that vouch-proxy is up and running, we need to update our nginx configuration in order to leverage it. Below is the full configuration to support vouch-proxy:
server {
listen 80 default_server;
listen [::]:80 default_server;
location ~ ^/(auth|login|logout|static) {
proxy_pass http://127.0.0.1:9090;
include /etc/nginx/proxy_params;
}
location = /validate {
proxy_pass http://127.0.0.1:9090/validate;
include /etc/nginx/proxy_params;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
}
error_page 401 = @error401;
location @error401 {
return 302 $scheme://$http_host/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
}
location / {
auth_request /validate;
proxy_pass http://localhost:8080;
include /etc/nginx/proxy_params;
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
}
}
As you can see, we have a few more blocks added to support vouch-proxy. A quick rundown is as follows:
location ~ ^/(auth|login|logout|static) {
proxy_pass http://127.0.0.1:9090;
include /etc/nginx/proxy_params;
}
This block is fairly straightforward. We are ensuring that /auth, /login, /logout and /static all get proxied to our vouch-proxy instance running on port 9090 with a minimal configuration.
location = /validate {
proxy_pass http://127.0.0.1:9090/validate;
include /etc/nginx/proxy_params;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
}
This block is the main integration point between our header_parrot application and vouch-proxy (we will see how under the main “location /” block). When /validate is hit on our webserver, the request will be passed off to vouch-proxy running on port 9090 to determine if the user has a valid login session from Okta or not. If it does, /validate will return a 200 and we will be allowed through to our header_parrot application. If the user does not have a valid login session /validate will return a 401 not authorized error, which is handled in the next section of our new nginx configuration below.
error_page 401 = @error401;
location @error401 {
return 302 $scheme://$http_host/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
}
This block handles when a user does not have a valid login session from Okta. vouch-proxy will return a 401, and we set up a location specifically for handling error 401. This will redirect any 401 to /login on our webserver with several additional items set in the URL that will allow vouch-proxy to initiate a login sequence for the user with Okta. In this case, the user would be redirected to a generic Okta login page (your experience may be different if your organization sets up custom Okta login pages) that would look similar to this:

Once logged into Okta, you will be redirected back to the application via the callback url provided and if everything was successful you will have access to your application.
Finally the last block of the new config, which pulls all of this together:
location / {
auth_request /validate;
proxy_pass http://localhost:8080;
include /etc/nginx/proxy_params;
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
}
In this block, the most important addition is the “auth_request /validate;” line. auth_request is an nginx module that implements client authorization based on the result of a subrequest. If the subrequest returns a 2xx response code, the access is allowed. If it returns 401 or 403, the access is denied with the corresponding error code.
What this means for our setup is that any request to our website will be passed into the /validate location, which is vouch-proxy running on 9090. If that request returns a 2xx then the request is allowed to proceed to our header_parrot app running on port 8080 internally. If the call to /validate returns a 401 we will redirect the call to a /login endpoint that will prompt the user to log in via Okta. Once that login is successful then the user will be redirected back through to the header_parrot application and things will flow as normal.
Now when we hit our header_parrot application we can see a few more headers being passed through.

Most notably would be the Cookie being passed in called VouchCookie (configurable in the vouch-proxy config.yml), and the X-Vouch-User, which can be leveraged for logging access or any other reason you might want to know who the user is.
Resources
Below are resources to review as you start down the path of combining nginx, vouch-proxy and okta.
https://github.com/vouch/vouch-proxy/
https://developer.okta.com/blog/2018/08/28/nginx-auth-request
http://nginx.org/en/docs/http/ngx_http_auth_request_module.html