YubiKey OTP Two-Factor Authentication with OpenVPN and Viscosity
After setting up your own OpenVPN server, you may want to enhance its security by requiring two-factor authentication. This guide will expand on setting up an OpenVPN server on Ubuntu by adding YubiKey OTP (One-Time Password) support using Viscosity's built-in Challenge/Request support.
YubiKey OTP authentication adds an extra step after the user's username and password have been accepted. Users activate their YubiKey to enter a one-time password, which is validated using Yubico's OTP validation service.
Preparation
For this guide, we assume:
- You have already installed the latest version of Ubuntu LTS (26.04 at time of writing)
- You have root access to this installation
- You have already set up and tested an OpenVPN server using our Setting up an OpenVPN server with Ubuntu guide
- You already have a copy of Viscosity installed on your client device and already set up for this server
- You have a Yubico YubiKey that supports OTP
This guide assumes you are already able to connect to your OpenVPN server using certificate/key authentication. This guide will add two more authentication steps: a username and password using PAM, and a YubiKey OTP challenge.
PAM authentication is the simplest form of username/password authentication we can use with OpenVPN. By default this means users authenticate using normal local Ubuntu accounts, so you do not need to manage a separate username/password database.
This guide uses the example YubiKey OTP authentication script from SparkLabs' OpenVPN two-factor authentication extensions. You can modify this script to use another primary authentication method, such as LDAP, if required.
Ubuntu
The following instructions for Ubuntu assume that you have already set up an OpenVPN server using our Setting up an OpenVPN server with Ubuntu guide.
Installing the YubiKey OTP Authentication Script
First, update your server and install the required packages:
sudo apt update
sudo apt install -y python3 python3-pam wget ca-certificates
Next, create a directory for the OpenVPN authentication script:
sudo install -d -m 0755 /usr/local/lib/openvpn
Download the YubiKey OTP authentication script:
sudo wget -O /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py https://raw.githubusercontent.com/thesparklabs/openvpn-two-factor-extensions/master/yubikey-otp-script/openvpn_pam_yubikey_otp.py
After the file is in place, make it executable:
sudo chmod 0755 /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py
Creating VPN Users
The YubiKey OTP authentication script uses PAM for username/password authentication. This means users authenticate using normal local accounts by default.
If you need to create a new local user, enter the following and follow the prompts:
sudo adduser exampleuser
Replace exampleuser with the username you want to create.
Getting Yubico API Credentials
The script validates YubiKey OTPs using Yubico's OTP validation service. To do this, you need a Yubico Client ID and Secret Key.
Go to https://upgrade.yubico.com/getapikey/ and follow the instructions to generate a Client ID and Secret Key. Your OpenVPN server must also be able to connect to Yubico's validation service at:
https://api.yubico.com/wsapi/2.0/verify
Setting up OpenVPN
Now we can enable YubiKey OTP authentication for the OpenVPN server. Edit your server configuration:
sudo nano /etc/openvpn/server/server.conf
Scroll to the bottom of the configuration and add the following:
# YubiKey OTP and PAM authentication
script-security 2
auth-user-pass-verify /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py via-file
client-crresponse /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py
# Yubico OTP validation details
setenv yubicoClientId YOURCLIENTID
setenv yubicoSecretKey YOURSECRETKEY
# Allow OpenVPN to reauthenticate during renegotiation without prompting for
# YubiKey OTP again.
auth-gen-token
Replace YOURCLIENTID and YOURSECRETKEY with the values copied from Yubico.
Save the configuration and restart OpenVPN:
sudo systemctl restart openvpn-server@server
To check the server status, enter:
sudo systemctl status openvpn-server@server
To which it should reply with:
Active: active (running)
If the server is not listed as active (running), you can view the OpenVPN server log by entering:
sudo journalctl -u openvpn-server@server -xe
The script will automatically create its local YubiKey OTP database the first time a challenge is generated. For new installations this database is stored at:
/var/lib/openvpn-yubikey-otp/token_index.sqlite3
How YubiKeys Are Assigned to Users
There is no separate enrollment command for YubiKey OTP. The first time a user successfully authenticates with a YubiKey OTP, the script stores that YubiKey's public ID against the username.
On later connections, that username must authenticate using the same YubiKey. If a user needs to change YubiKeys, delete their stored YubiKey OTP enrolment using the command in the Advanced section below. The next successful connection will assign the new YubiKey to that user.
Setting up Viscosity
Now the server is set up, you only need to make a single change to your connection in Viscosity:
- Open Viscosity's Preferences and edit your connection.
-
Go to the Authentication tab and tick 'Use Username/Password authentication'
-
Save the connection
Upgrading From an Older YubiKey OTP Script
Older versions of this guide required adding a static-challenge command to the Advanced commands for the Viscosity connection. This is no longer needed, as the updated script sends the YubiKey OTP challenge prompt to Viscosity automatically.
If you are upgrading from an older copy of the script, edit the connection in Viscosity, go to the Advanced tab, and remove any line similar to:
static-challenge "Activate your YubiKey" 0
Save the connection after removing it.
Older versions of the script stored YubiKey public IDs in a different database format, usually at:
/etc/openvpn/token_index.db
The updated script uses a SQLite database, stored by default at:
/var/lib/openvpn-yubikey-otp/token_index.sqlite3
Do not set yubicoTokenDbPath to the old token_index.db file. If you are upgrading an existing setup, either let users reassign their YubiKeys on their next successful connection, or manually migrate the old token data into the new SQLite database before switching over.
Using Viscosity
Now that the changes to your configuration in Viscosity are finished, you can connect to the VPN server to test the connection.
When you connect, you'll be prompted to enter your username and password. After these are accepted, Viscosity will prompt you to activate your YubiKey. Activate your YubiKey to enter its OTP, and the VPN connection will proceed if authentication was successful.
Advanced
Managing Enrolled Users
The YubiKey OTP authentication script includes commands for listing, inspecting, and deleting enrolled users.
To list enrolled users:
sudo /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py list
To show details for a user:
sudo /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py show exampleuser
To delete a user's YubiKey OTP enrolment:
sudo /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py delete exampleuser
Replace exampleuser with the user's username.
Deleting a user's YubiKey OTP enrolment also removes any pending YubiKey OTP challenges for that user. If the user should continue to have VPN access, their next successful connection will assign the YubiKey they use to their account.
Customising the YubiKey OTP Challenge
By default, users have 300 seconds to answer the YubiKey OTP challenge, and the challenge text is:
Please activate your YubiKey
You can customise these values by adding yubicoChallengeTimeout and yubicoChallengeText to your OpenVPN server configuration:
setenv yubicoChallengeTimeout 300
setenv yubicoChallengeText "Please activate your YubiKey"
After changing these values, restart OpenVPN:
sudo systemctl restart openvpn-server@server
Using a Different Token Database Path
The default YubiKey OTP database path is:
/var/lib/openvpn-yubikey-otp/token_index.sqlite3
Advanced administrators can use a different database path by setting yubicoTokenDbPath in the OpenVPN server configuration:
setenv yubicoTokenDbPath /path/to/token_index.sqlite3
When running admin commands manually, include the --db option if you are using a custom database path:
sudo /usr/local/lib/openvpn/openvpn_pam_yubikey_otp.py --db /path/to/token_index.sqlite3 list
After changing the OpenVPN configuration value, restart OpenVPN:
sudo systemctl restart openvpn-server@server