Tutorial: Implementing a Challenge/Response Authentication in Access Server
Enhance Access Server's authentication security with dynamic or static challenge/response protocols.
Overview
Access Server supports challenge/response authentication, which adds an extra layer of security to certificates or credentials. This feature is typically used for multi-factor authentication (MFA) or custom authentication solutions.
While Access Server includes built-in TOTP MFA functionality for a simple MFA solution, custom solutions such as hardware token authentication or challenge questions can be implemented via a post-authentication (post-auth) script.
An installed Access Server.
Console access and the ability to get root access.
Familiarity with writing Python scripts for use as post-auth scripts in Access Server.
Dynamic challenge/response protocol
In the dynamic protocol, the VPN client isn't initially aware of the challenge requirement. Instead, the challenge is presented after the user submits their login credentials or certificate. This is typically used when the challenge changes for each session, such as a time-based one-time password (TOTP) or a hardware token.
Process Overview:
The user attempts to authenticate with their certificate or credentials.
The server issues a challenge, temporarily failing the authentication.
The client prompts the user to answer the challenge.
The user submits the challenge response.
The server validates the response and completes the authentication process if the response is correct.
This dynamic approach is useful when you want flexible challenge text per session and when you want to implement the challenge after VPN clients have already installed connection profiles.
Important
For command-line clients on platforms like Linux, you may need to add the auth-retry-interact
directive to have the client properly prompt the user for the challenge response.
Static challenge-response protocol
In the static protocol, the VPN client already knows about the challenge requirement because the static challenge is embedded in the connection profile. This method is more efficient but only suitable for VPN sessions, not web-based ones.
Process Overview:
The client already knows the challenge via the connection profile.
The client displays the challenge to the user.
The user submits their response along with their login credentials.
The server processes both the login credentials and the challenge response in one transaction.
If the response is correct, the client is authenticated.
The static protocol is ideal when the challenge text is constant for all login sessions. However, if static challenge support is unavailable for a session (such as in web authentication), the system should fall back to dynamic challenge mode.
You will use a post-authentication script to implement a dynamic challenge/response. Here's a basic outline of how the script will function:
def post_auth(authcred, attributes, authret, info): # Check if this is a VPN authentication session if attributes.get('vpn_auth'): # If no challenge response is provided, issue a challenge if 'static_response' not in authcred: authret['status'] = FAIL authret['client_reason'] = "Enter the verification code from your authenticator." return authret else: # Validate the challenge response (implement custom logic here) challenge_response = authcred['static_response'] if challenge_response == "expected_value": # Replace with actual logic authret['status'] = SUCCEED else: authret['status'] = FAIL authret['client_reason'] = "Invalid verification code." return authret
Steps to enable the dynamic protocol:
Write a post-auth script similar to the one above.
Transfer your post-auth script to your Access Server.
Load the script using the following command:
cd /usr/local/openvpn_as/scripts/ ./sacli --key "auth.module.post_auth_script" --value_file="path_to_your_script.py" ConfigPut ./sacli start
Now, clients will be prompted for the additional challenge when they sign in, and the dynamic challenge response will be validated.
To use the static protocol, you will need to add the static-challenge
directive in the connection profile.
Edit the connection profile
.ovpn
file to add thestatic-challenge
directive. For example:static-challenge "Enter your verification code" 11
The second parameter (
1
) indicates that the user input should not be echoed (i.e., the input will be hidden).Modify the post-auth script to account for the static challenge by checking for the
static_response
field. For example:def post_auth(authcred, attributes, authret, info): if 'static_response' in authcred: challenge_response = authcred['static_response'] if challenge_response == "expected_value": # Replace with actual logic authret['status'] = SUCCEED else: authret['status'] = FAIL authret['client_reason'] = "Invalid verification code." else: # Fallback to dynamic challenge if no static response is provided authret['status'] = FAIL authret['client_reason'] = "Enter the verification code from your authenticator." return authret
Load the modified post-auth script:
cd /usr/local/openvpn_as/scripts/ ./sacli --key "auth.module.post_auth_script" --value_file="path_to_your_script.py" ConfigPut ./sacli start
Tip
Ensure that the static challenge text remains constant across sessions.
The static protocol is faster because it reduces the number of back-and-forth communications needed for authentication.