Post-Authentication Python Script Guide
Post-authentication (post-auth) scripts enable programmatic management of VPN client authentication after the initial process. Using the parameters documented here, you can write custom Python scripts to enhance Access Server's authentication workflows.
Important
OpenVPN Inc. provides examples of post-auth scripts, but we don't offer custom ones. Use the example scripts and documentation provided to develop or modify the post-auth script using the Python3 programming language.
Overview
Post-auth scripts allow developers to add custom logic after authentication, including user validation, IP address assignment, group designation, and more.
Post-auth means post-authentication and it uses a custom Python3 script loaded into Access Server that can add checks and challenges.
The process is such that Access Server manages initial user authentication then hands control over to the
post-auth
function for any final steps. If your script setsAUTH_NULL
toTrue
, then Access Server doesn't manage any authentication and it's entirely done in the script.Related resources:
Caution
If you’re looking to implement a simple MFA solution such as TOTP MFA in Access Server, we recommend you use the existing options in the Admin Web UI for MFA, which don't require a post-auth script.
The post-auth function
The post_auth
function is the core of the post-authentication script and receives the following parameters:
def post_auth(authcred, attributes, authret, info): ...
Where the parameters are:
authcred: Contains the client's authentication details.
attributes: Holds client-reported information.
authret: The status and result of authentication, which the script can modify.
info: Additional context provided based on the authentication method.
authret — authentication return value
authret
is a dictionary passed to and from the post_auth
function in the post-authentication script. It contains the authentication result and can be modified to control the outcome of the authentication process. The values within authret
can be adjusted by your script to allow or reject the user, set user properties, or modify other aspects of the authentication flow.
When the script is executed, authret
contains the authentication status and various parameters that the script can modify based on the needs of your authentication logic. If you change any of these values, it directly influences the authentication flow.
How authret controls authentication
The authret
dictionary is key in managing the authentication process within the post-auth script. It's passed to the script as part of the authentication process and contains important information about the current user’s authentication state. By modifying the values within authret
, you can control the outcome of the authentication process, set user properties, and enforce specific conditions.
Use the script to determine whether authentication succeeds or fails by modifying the status
field within authret
. You can also modify other fields to manage user-specific configurations like group assignments, static IP addresses, or permission settings.
This is a summary of how it works:
Authentication Status (
status
): Thestatus
field withinauthret
determines whether the authentication attempt will succeed or fail. By setting this value to 0 (SUCCEED), the authentication will pass, allowing the user to connect. Setting it to 1 (FAIL) will reject the authentication attempt. If you don’t modify thestatus
, the authentication process proceeds as usual.Group Assignment (
conn_group
): Theconn_group
field designates the user to a specific group. Typically, user properties inherited from the group are retrieved before the post-auth script runs. If you change the user's group by modifyingconn_group
within the script, those previously retrieved properties may be outdated. After the script completes, you can optionally setGROUP_SELECT = True
to instruct Access Server to look up the group-inherited properties. Note that any user properties set in the script will still take precedence.IP Assignment (
conn_ip
): Theconn_ip
field assigns a static IP address to the user. If you're assigning a static IP, ensure the IP falls within a group subnet. Access Server will try to assign the IP from the global static pool if no group is assigned. This ensures that the user receives a consistent IP address for their session.Failure Reason (
reason
andclient_reason
): Thereason
field is used to log the failure reason in the server logs. If authentication fails, you can use theclient_reason
field to send an error message back to the client, which will be shown to the user in the client application. These fields help diagnose why the authentication failed.Persisting Changes (
proplist
): Theproplist
dictionary holds various user properties that can be set or modified during authentication. You can customize the user's permissions and settings by changing values in this dictionary, such asprop_superuser
,prop_autogenerate
, orprop_autologin
. These properties can be returned inauthret
to affect the current session.
Post-auth script parameters (input)
authcred | A dictionary containing the following details about the authentication request: | ||
username | (string) User's username as provided by the end user. | ||
client_ip_addr | (string) Real IP address of the VPN client. | ||
client_hw_addr | (string, optional) MAC address of the VPN client. | ||
static_response | (string, optional) User's response to a custom challenge. | ||
attributes | A dictionary that contains client-reported values, including: | ||
client_info | A dictionary of strings provided by the client, including: | ||
UV_ASCLI_VER | (string) The version number of the connecting Access Server client. | ||
IV_PLAT | (string) Client platform ( | ||
UV_PLAT_REL | (string) Specific version of client platform. | ||
UV_APPVER_<APP_NAME> | (string) Version number of a specified app installed on the client. | ||
IV_HWADDR | (string) Primary network interface MAC address. | ||
UV_UUID | (string) Unique identifier for the VPN client device. | ||
vpn_auth | (boolean) | ||
reauth | (boolean) | ||
authret | A dictionary that contains the authentication status and may be modified to influence the authentication process. |
Authret output parameters
status | (int, required) Defines the authentication result: 0 = success, 1 = failure, 5 = MFA required. | |
user | (string, required) The canonical username of the user after authentication. This may differ from the authcred['username']. | |
reason | (string, optional) Message output to the log file when authentication fails. | |
client_reason | (string, optional) Message shown to the user if authentication fails. | |
proplist | (optional) A dictionary of properties for the connecting user, including: | |
conn_group | (string) Group designation for the user. | |
conn_ip | (string) Static IP address assigned to the user. | |
prop_superuser | (boolean) Assigns admin privileges to the user. | |
prop_autogenerate | (boolean) If | |
prop_autologin | (boolean) If | |
prop_deny_web | (boolean) Denies access to the web server and XML/REST web services, but still allows VPN access. | |
prop_lzo | (boolean) Enables LZO compression (deprecated). | |
prop_reroute_gw_override | (string) Overrides gateway routing settings for the user, with possible values of
|
Additional information
info | A dictionary that contains the following members, depending on the current | |
auth_method | (string) Specifies the authentication method, such as | |
LDAP specific: | ||
ldap_context | (object) A Python LDAP context object that can be used for additional LDAP queries. We use this in our LDAP group mapping script. | |
user_dn | (string) The LDAP distinguished name of the user authenticating, used to normalize the username to what the LDAP server reports. | |
RADIUS specific: | ||
radius_reply | (dictionary) Attributes received from the RADIUS server in the successful authentication reply. We use this in our RADIUS group mapping script. | |
SAML specific: | ||
saml_attr | (dictionary) Attributes received from the SAML server in the successful authentication reply. |
Writing the post-auth script
In this section, we'll walk through creating a custom post-auth script. You'll find two basic examples. The first example demonstrates assigning a user a group and static IP address. The second example shows how to limit VPN access to a user in a specified group.
Example 1
This post-auth script provides a simple example of how post-authentication actions can be used to control group assignments and IP addresses based on user identity. Below is the example script, followed by descriptions of what it does.
from pyovpn.plugin import * def post_auth(authcred, attributes, authret, info): # This checks if the user is 'brandonopenvpn' if authcred['username'] == 'brandonopenvpn': authret['conn_group'] = 'VPN_USERS' # Assign user to VPN_USERS group authret['conn_ip'] = '192.0.2.15' # Assign a static IP address authret['status'] = SUCCEED # If the user is not 'brandonopenvpn', # allow the connection with no extra actions else: authret['conn_group'] = 'default_group' return authret
This script assigns the user 'brandonopenvpn' to the 'VPN_USERS' group and gives them a static IP address (192.0.2.15). All other users are assigned to the 'default_group' without specific IP assignments. The script ensures the status is set to SUCCEED for 'brandonopenvpn,' while other users proceed through normal authentication.
The script imports the necessary functions from the
pyovpn.plugin
module to interface with Access Server's authentication mechanisms.from pyovpn.plugin import *
Then it defines the
post_auth
function, which takes four parameters:authcred
: Contains the credentials and details about the authenticated user.attributes
: Contains client-reported values such as platform and application details.authret
: The dictionary used to modify the application process, such as setting the group and IP address for this example.info
: Contains additional information, like authentication method.
def post_auth(authcred, attributes, authret, info):
Next, it checks if the user is 'brandonopenvpn' and, if so, takes actions to assign the user to the 'VPN_USERS' group (by modifying authcred['username']) with the static IP address, 192.0.2.15 (by modifying authret[conn_ip']). And the authentication is successful.
if authcred['username'] == 'brandonopenvpn': authret['conn_group'] = 'VPN_USERS' # Assign user to VPN_USERS group authret['conn_ip'] = '192.0.2.15' # Assign a static IP address authret['status'] = SUCCEED # Mark authentication as successful
For all other users, after authenticating, the script assigns them to the default group, 'default_group', without setting a static IP address.
else: authret['conn_group'] = 'default_group' # Assign non-'brandonopenvpn' users to the default group
Finally, the script returns the modified
authret
dictionary, which Access Server uses to apply the specific actions (assign the group and static IP).return authret
Example 2
This second post-auth script example demonstrates a simple approach to restricting access based on group membership.
from pyovpn.plugin import * def post_auth(authcred, attributes, authret, info): # get user's property list, or create it if absent proplist = authret.setdefault('proplist', {}) # get group name for the user conn_group_save = proplist.get('conn_group') # saved group # This checks if user is member of 'VPN_USERS' group if conn_group_save == "VPN_USERS": authret['status'] = SUCCEED # If the user is not in the group, fail authentication else: authret['status'] = FAIL # Fail the authentication authret['reason'] = 'User not in the required group' return authret
The script checks for the user's saved group in the proplist
dictionary and grants access if they are in the 'VPN_USERS' group. Any other user can't authenticate and thus can't make a VPN connection.
The script imports the necessary functions from the
pyovpn.plugin
module to interface with Access Server's authentication mechanisms.from pyovpn.plugin import *
Then it defines the
post_auth
function, which takes four parameters:authcred
: Contains the credentials and details about the authenticated user.attributes
: Contains client-reported values such as platform and application details.authret
: The dictionary used to modify the application process, such as setting the group and IP address for this example.info
: Contains additional information, like authentication method.
def post_auth(authcred, attributes, authret, info):
Next, it retrieves or initializes the
proplist
dictionary containing user properties. If it's not present, the script will create it.proplist = authret.setdefault('proplist', {})
After, it checks the saved group name (
conn_group_save
) for the user by looking up the value from theproplist
dictionary.conn_group_save = proplist.get('conn_group') # saved group
If that group name is 'VPN_USERS', the script sets the
status
toSUCCEED
, allowing authentication to proceed. If they're not in the group, the script sets thestatus
toFAIL
, and a reason ('User not in the required group'
) is added to the authentication failure.if conn_group_save == "VPN_USERS": authret['status'] = SUCCEED # Allow access for VPN_USERS group else: authret['status'] = FAIL # Fail the authentication authret['reason'] = 'User not in the required group' # Reason for failure
Finally, the script returns the modified
authret
dictionary, which Access Server uses to apply the specific actions (assign the group and static IP).return authret
To test one of these post-auth scripts, follow these steps:
Ensure your script is installed properly in the appropriate Access Server script directory.
Connect to your console and get root privileges.
Use the
authcli
tool to simulate authentication:./authcli -u <username>
Enter the password when prompted.
Next, follow the steps for the example script you're testing.
When testing example 1, the expected output for 'brandonopenvpn' should include the static IP 192.0.2.15 and the conn_group
set to 'VPN_USERS'.
For 'brandonopenvpn':
API METHOD: authenticate AUTH_RETURN status : SUCCEED user : brandonopenvpn reason : RADIUS access accepted auth method : radius proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'} conn_group : VPN_USERS conn_ip : 192.0.2.15
For another user (e.g., 'laurenopenvpn'):
API METHOD: authenticate AUTH_RETURN status : SUCCEED user : laurenopenvpn reason : RADIUS access accepted auth method : radius proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'} conn_group : default_group
When testing example 2, only users in the 'VPN_USERS' group can authentication successfully. All others have their authentication failed.
For 'brandonopenvpn' (a member of the 'VPN_USERS' group):
API METHOD: authenticate AUTH_RETURN status : SUCCEED user : brandonopenvpn reason : RADIUS access accepted auth method : radius proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect', 'conn_group': 'VPN_USERS'}
For a different user (e.g., 'laurenopenpn', not in the 'VPN_users' group):
API METHOD: authenticate AUTH_RETURN status : FAIL user : laurenopenvpn reason : User not in the required group auth method : radius proplist : {'prop_autogenerate': 'true', 'prop_force_lzo': 'false', 'type': 'user_connect'}
Summary for these example scripts
Example 1: Assigns the user to a specific group ('VPN_USERS') and provides a static IP address if the user matches the condition in the script.
Example 2: Only allows users from the 'VPN_USERS' group to authenticate successfully. Any other users will have their authentication failed with a custom reason ("User not in the required group").
By using the authcli
tool and reviewing the output, you can confirm that the post-authentication script is functioning as expected and that the correct parameters are being returned.
Return value
The post-auth function should return authret
or (authret
, proplist_save
).
Returning only
authret
affects only the current authentication session.Returning
proplist_save
along withauthret
saves the changes to the user properties database, making them persistent for future sessions. We use this in our hardware-address checking, RADIUS, LDAP, and SAML post-auth scripts.
Install the post-auth script
Refer to these tutorials: