Home Linux Enable 2FA on FreeRADIUS with OpenLDAP Users

Enable 2FA on FreeRADIUS with OpenLDAP Users

by Lakindu Jayasena
16.1k views 10 mins read
Enable 2FA on FreeRADIUS with OpenLDAP

Two-Factor authentication (aka 2FA) is a type of multi-factor authentication that adds an extra layer of protection to traditional username and password by requiring users to enter an authentication code (second factor), provided by your virtual/hardware MFA solution.

Google has provided one of those virtual authenticators called Google Authenticator that implements two-step verification services using the Time-based One-time Password Algorithm and HMAC-based One-time Password algorithm for authenticating users of mobile applications by Google. Authenticator generates a six-digit one-time password which users must enter in addition to their usual login details.

In this article, I will talk about integrating the Google Authenticator PAM module to FreeRADIUS and authenticating with enterprise users inside OpenLDAP. Any hosts that are connected to this RADIUS server will have the 2FA functionality. Note that for this demonstration, I’m using Ubuntu 20.04 as an OS distribution.

About FreeRADIUS and Google Authenticator PAM

RADIUS (Remote Authentication Dial-in User Service) is a standardized authentication framework that can be used to authenticate many different devices including VPNs, Routers, Switches, Computers, and many more. While there are several RADIUS software, FreeRADIUS is one of the most popular RADIUS software of choice in the open-source world.

PAM (Pluggable Authentication Module) is for Linux system users and password authentication. Since it has a PAM, this is also suitable for integrating it with the Google Authenticator PAM module. Google Authenticator PAM is a great free module that allows FreeRADIUS to talk to Google Authenticator.

Installing FreeRADIUS and Google Authenticator PAM Module

Installing FreeRADIUS and Google Authenticator on Ubuntu 20.04 is very easy and having only simple steps. All we need is to issue the following command lines.

 apt-get update apt-get upgrade apt-get install freeradius freeradius-common freeradius-utils freeradius-ldap apt-get install libpam-google-authenticator 

Since we are using Google Authenticator configuration is TOTP based, make sure to properly syncing the time correctly between the location where you generate the Google Authenticator TOTP and the virtual MFA device.

Configuring FreeRADIUS

Once the required packages are installed, the next step is to configure FreeRADIUS by editing configuration files. There are three main configuration files we need to edit to get this setup done.

The first config file that we need to edit is the /etc/freeradius/3.0/radiusd.conf file.

 # SECURITY CONFIGURATION security { # user/group: The name (or #number) of the user/group to run radiusd as. user = root group = root } 

The next config file that we need to edit is the /etc/freeradius/3.0/users file. This file will authorize FreeRADIUS to use LDAP users who are members of a specific LDAP group as the default and reject if any other methods.

 DEFAULT Ldap-Group == "cn=all.employees,ou=staff,dc=example,dc=com" Reply-Message = "You are Accepted" DEFAULT Auth-Type := Reject 

The last configuration file on FreeRadius we need to edit is /etc/freeradius/3.0/sites-enabled/default. This file tells FreeRADIUS how to do the authorization and authentication with PAM.

 server default { authorize { # # Some broken equipment sends passwords with embedded zeros. # i.e. the debug output will show # # User-Password = "password\000\000" # # This policy will fix it to just be "password". # # filter_password filter_uuid filter_google_otp # # The ldap module reads passwords from the LDAP database. ldap } authenticate { # # PAP authentication, when a back-end database listed # in the 'authorize' section supplies a password. The # password can be clear-text, or encrypted. Auth-Type PAP { pap if (&Google-Password) { update request { &User-Name := "%{&User-UUID}" &User-Password := "%{&Google-Password}" } pam } else { update reply { Reply-Message := "Login incorrect: TOTP Fail" } reject } } } } 

The above configuration snippet uses two new filters (“filter_uuid” & “filter_google_otp” ) defined inside the /etc/freeradius/3.0/policy.d/filter that helps to extract the username part of the input email address and the 6 digit TOTP from the password.

 filter_uuid { if (&User-Name =~ /^(.*)@example\.com$/) { update request { &User-UUID := "%{1}" } } } filter_google_otp { if (&User-Password =~ /^(.*)([0-9]{6})$/) { update request { &Google-Password := "%{2}" &User-Password := "%{1}" } } } 

Since we are using new attributes (“User-UUID” & “Google-Password”), we need to map between the names used by people and the binary data in the RADIUS packets because names have no meaning in the protocol. Hence add the new attributes as follows inside the /etc/freeradius/3.0/dictionary file.

 ATTRIBUTE Google-Password 3000 string ATTRIBUTE User-UUID 3001 string 

Setup FreeRadius Clients

This is the place where we can set up our secret key that is used by the clients (Eg: Network Devices or Servers) to connect to the RADIUS server. Make sure to change the default secret key to a different key for better security.

 client MyRouter1 { ipaddr = myrouter.example.com secret = testing123# } 

Configure FreeRadius with OpenLDAP

Now let’s start to configure the radius LDAP module to connect to the OpenLDAP server. The first thing we are going to do is edit the file /etc/freeradius/3.0/mods-available/ldap as follows.

 ldap { server = 'ldap.example.com' identity = 'cn=admin,dc=example,dc=com' password = 'EnterBindUserPassword' base_dn = 'dc=example,dc=com' # # User object identification. # user { # Where to start searching in the tree for users base_dn = "ou=staff,dc=example,dc=com" # Filter for user objects, should be specific enough # to identify a single user object. filter = "(mail=%{%{Stripped-User-Name}:-%{User-Name}})" } # # User membership checking. # group { # Where to start searching in the tree for groups base_dn = "ou=staff,dc=example,dc=com" # Filter for group objects, should match all available # group objects a user might be a member of. filter = '(objectClass=GroupOfNames)' # Attribute that uniquely identifies a group. # Is used when converting group DNs to group # names. name_attribute = "cn=example,ou=group" # Filter to find group objects a user is a member of. # That is, group objects with attributes that # identify members (the inverse of membership_attribute). membership_filter = "(|(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn}))(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn})))" # The attribute in user objects which contain the names # or DNs of groups a user is a member of. membership_attribute = 'memberOf' } } 

Once configured the FreeRadius LDAP module, enable it by issueing following commnds.

 cd /etc/freeradius/3.0/mods-enabled ln -s ../mods-available/ldap . 

Configuring FreeRADIUS with Google Authenticator PAM

Since we configured FreeRADIUS to use PAM + LDAP to authenticate users, we need to configure the /etc/pam.d/radiusd file and instruct it to integrate Google Authenticator PAM. Add the following 1st line and comment out the rest of the lines as follows.

 auth required /usr/lib/x86_64-linux-gnu/security/pam_google_authenticator.so forward_pass #@include common-auth #@include common-account #@include common-password #@include common-session 

Now enable the PAM module on FreeRadius.

 cd /etc/freeradius/3.0/mods-enabled ln -s ../mods-available/pam . 

Finally as usual in Linux, when all configuration files has been changed, then the service needs to be restarted for the changes to take effect.

 systemctl restart freeradius.service 

Testing RADIUS Authentication with LDAP

Before starting the testing, you need to create the required local Linux users which username same as the LDAP user account name and also need to configure Google Authenticator on that Linux user.

I’ve already covered the configuration of the Google Authenticator secret keys for Linux users in my previous article “Secure AWS EC2 Instances with Multi-Factor Authentication“, so look for the Configuring Google Authenticator section. Once you are done generating secret keys, come back to this page.

Note: The Linux user inside the RADIUS server does not require any password that user account only needs to store Google Authenticator secret keys.

FreeRADIUS software package includes a simple tool that we can use to directly query the daemon with requests. 

 #Command Syntax radtest <username> <password+google authenticator TOTP> localhost 1812 <RADIUS secret key> #Example: radtest [email protected] ldapuserpassword123456 localhost 1812 testing123# 
2FA enabled FreeRADIUS Testing with OpenLDAP User


This article describes only one of many possible configurations. In here we used the Google Authenticator PAM to integrate with FreeRADIUS, and then connect FreeRADIUS with OpenLDAP for user authentication. We created a local Linux user inside the RADIUS server because to store the Google Authenticator secret keys and that account not required a password.

You can use this RADIUS server for any kind of radius-enabled clients (Network Devices, SSL VPN solutions, &, etc…) to have a more secure 2FA solution for your organization.

Related Articles


freerad_guy August 24, 2021 - 9:35 AM


The behavior of my configuration is strange, the password needs to be completed with the TOTP otherwise it doesn’t authenticate but I can put whatever number and it works. It seems that the freeradius does not check whether the 6 numbers of the TOTP are correct, do you have an idea ?

Manish Taneja October 23, 2021 - 2:04 PM

I have the same issue. Was there a fix for this?

Alex March 11, 2022 - 6:35 PM

Hello Lakindu, thanks for this post! Let me ask you a little question please. I get an error in the $radiusd -X log.

including dictionary file /usr/share/freeradius/dictionary
including dictionary file /usr/share/freeradius/dictionary.dhcp
including dictionary file /usr/share/freeradius/dictionary.vqp
Errors reading /etc/raddb/dictionary: dict_init: /etc/raddb/dictionary[50] invalid keyword “ATTRIBUTE      ”

I followed all step you mentioned with my config but seems to be a problem with the dictionary.
Do you have any tip for this? I didn’t find anything.

Thanks in advance.


Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.