Skip to content

Vouch Proxy

https://github.com/vouch/vouch-proxy

Authentication in nginx with SSO providers (OKTA, Azure AD etc.)

Requirements

  • nginx
  • nginx auth_request module (included in most nginx packages)
  • manual nginx configuration (see below example)
  • go
  • OKTA application (you need to be OKTA admin)
  • Organization domain: domain.company.com
  • Client id - Vouch Proxy connects to OKTA with this
  • Client secret - Vouch Proxy authenticates to OKTA with this
  • Sign-in redirect URIs - OKTA sends response with this

Steps

  • Download source code
  • Build application bin
  • Copy application config
  • Create application systemd service
  • Run application
  • Change nginx configuration
  • Reload nginx to apply configuration

Example nginx config

This will set an HTTP header X-Vouch-User with the value of $auth_resp_x_vouch_user that your backend server can read in order to know who logged in.

# For Vouch Proxy large cookie support
# large_client_header_buffers 4 16k;
# proxy_buffer_size 16k;

location ~ ^/(auth|login|logout|static) {
    proxy_pass http://127.0.0.1:9090;
    proxy_set_header Host $http_host;
}

location = /validate {
    # forward the /validate request to Vouch Proxy
    proxy_pass http://127.0.0.1:9090/validate;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header Host $http_host;

    auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
    auth_request_set $auth_resp_x_vouch_idp_claims_groups $upstream_http_x_vouch_idp_claims_groups;

    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;
}

# if validate returns `401 not authorized` then forward the request to the error401 block
error_page 401 = @error401;

location @error401 {
    # redirect to Vouch Proxy for login
    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 / {
    # send all requests to the /validate endpoint for authorization
    auth_request /validate;

    # get Vouch Proxy claims into a local nginx variable
    auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
    auth_request_set $auth_resp_x_vouch_idp_claims_email  $upstream_http_x_vouch_idp_claims_email;
    auth_request_set $auth_resp_x_vouch_idp_claims_profile  $upstream_http_x_vouch_idp_claims_profile;
    auth_request_set $auth_resp_x_vouch_idp_claims_groups $upstream_http_x_vouch_idp_claims_groups;
    auth_request_set $auth_resp_x_vouch_idp_idtoken $upstream_http_x_vouch_idp_idtoken;
    auth_request_set $auth_resp_x_vouch_idp_accesstoken $upstream_http_x_vouch_idp_accesstoken;

    # forward the claim to the proxied server
    # for FastCGI you use `fastcgi_param PARAM_NAME $param-value;`
    #   That you access in PHP with `$_SERVER['PARAM_NAME']`
    # For proxy_pass you use `proxy_set_header Header-Name $header_value;`
    fastcgi_param REMOTE_VOUCH_USER $auth_resp_x_vouch_user;
    fastcgi_param REMOTE_EMAIL $auth_resp_x_vouch_idp_claims_email;
    fastcgi_param REMOTE_PROFILE $auth_resp_x_vouch_idp_claims_profile;
    fastcgi_param REMOTE_GROUPS $auth_resp_x_vouch_idp_claims_groups;
    fastcgi_param REMOTE_IDTOKEN $auth_resp_x_vouch_idp_idtoken;
    fastcgi_param REMOTE_ACCESSTOKEN $auth_resp_x_vouch_idp_accesstoken;


    # forward authorized requests to your service
    include snippets/fastcgi-php.conf;
    fastcgi_pass 127.0.0.1:9000;
}

Example vouch-proxy config

vouch:
  logLevel: info

  listen: 127.0.0.1
  port: {{ vouch_proxy_port }}

  # domains:
  # valid domains that the jwt cookies can be set into
  # the callback_urls will be to these domains
  # domains:
  # - yourdomain.com
  # - yourotherdomain.com

  # - OR -
  # instead of setting specific domains you may prefer to allow all users...
  # set allowAllUsers: true to use Vouch Proxy to just accept anyone who can authenticate at the configured provider
  # and set vouch.cookie.domain to the domain you wish to protect
  allowAllUsers: true

  jwt:
    # - if `./config/secret` doesn't exist then randomly generate a secret and store it there (service user needs permission)
    secret: {{ jwt_secret }}
    maxAge: 5

  cookie:
    name: VouchCookie
    # allow the jwt/cookie to be set into http://yourdomain.com (defaults to true, requiring https://yourdomain.com)
    secure: false
    # vouch.cookie.domain must be set when enabling allowAllUsers
    domain: company.com
    # Set cookie maxAge to 0 to delete the cookie every time the browser is closed.
    maxAge: 0

  headers:
    jwt: X-Vouch-Token
    user: X-Vouch-User
    querystring: access_token
    redirect: X-Vouch-Requested-URI
    idtoken: X-Vouch-IdP-IdToken
    accesstoken: X-Vouch-IdP-AccessToken

    # claims - a list of claims that will be stored in the JWT and passed down to applications via headers
    # By default claims are sent down as headers with a prefix of X-Vouch-IdP-Claims-ClaimKey
    claims:
      - openid
      - groups
      - email
      - profile
    # these will result in headers being passed back to nginx as the headers
    #   X-Vouch-IdP-Claims-Groups: groupa, groupb, groupc
    #   X-Vouch-IdP-Claims-Given-Name: Robert
    # nginx will populate the variables
    #   $auth_resp_x_vouch_idp_claims_groups
    #   $auth_resp_x_vouch_idp_claims_given-name

  session:
    name: VouchSession



oauth:
  provider: oidc
  client_id: {{ okta_client_id }}
  client_secret: {{ okta_client_secret }}
  auth_url: https://sso.provicer.com/oauth2/v1/authorize
  token_url: https://sso.provider.com/oauth2/v1/token
  user_info_url: https://sso.provider.com/oauth2/v1/userinfo
  scopes:
    - openid
    - groups
    - email
    - profile
  callback_url: {{ callback_url }}

Example SystemD service unit

[Unit]
Description=Vouch Proxy
After=network.target

[Service]
Type=simple
DynamicUser=yes
WorkingDirectory=/opt/vouch_proxy
ExecStart=/opt/vouch_proxy/vouch-proxy
Restart=on-failure
RestartSec=5
StartLimitInterval=60s
StartLimitBurst=3
TimeoutSec=900

[Install]
WantedBy=default.target