Forum OpenACS Development: Multi-Factor Authentication in OpenACS

Greetings to all,

I'm interested in developing two C-based modules that can enable two-factor authentication in OpenACS. I'm not planning to change the OpenACS login flow at this stage, but I'm open to feedback and suggestions from the community -- if there is interest I could do that as well.

Along with the C-based modules, I'll provide a sample project that demonstrates how to set up two-factor authentication with google authenticator and a page that verifies the MFA code.

I would appreciate any comments or suggestions on this project. Thanks for reading!

Collapse
Posted by Neophytos Demetriou on
You can follow the development here: https://github.com/jerily/tmfa

This is the first working version. I will do the QR code image generator next.

Collapse
Posted by Neophytos Demetriou on
Initial import of QR code generator NaviServer module: https://github.com/jerily/tqrcodegen

That completes the prerequisites for this project.
I will most likely do the sample OpenACS package tomorrow.
In other words, the sample package will show that two factor authentication is now possible in OpenACS.

If there are any questions, please do not hesitate and contact me.

Collapse
Posted by Neophytos Demetriou on
This is ready, including the sample-2fa package. You can see a screenshot here:
https://github.com/jerily/openacs-packages/tree/main/sample-2fa

I'll prepare a Dockerfile later in the afternoon and update readme with installation instructions.

Collapse
Posted by Neophytos Demetriou on
I have added a Dockerfile so that you can try this out. Here is how:

git clone https://github.com/jerily/openacs-packages.git
cd openacs-packages
docker build . -t neo-openacs-packages:latest
docker run --network host neo-openacs-packages:latest

Once you run the last command, you can point your browser to http://localhost:8000/ and login with the following credentials:

email: test at example dot com
password: test

PS. The OpenACS instance in the docker image also includes the work I did on semantic search so you can try it out without building multiple images.

Collapse
Posted by Claudio Pasolini on
Hi Neophytos,

I looked at your work and I wondered if it was possible to avoid the external libraries taht you used to generate the one-time password and the QRcode, so as to build a cleaner solution.

I think that with the help of NaviServer's ns_totp and the CLI qrencode it should be possible.

All the best,

Claudio

Collapse
Posted by Neofytos Dimitriou on
Hi Claudio, It should be possible but I haven't tried with ns_totp. I think Gustaf had done so way back when.
Collapse
Posted by Claudio Pasolini on

I wanted to try the generation of the QRcode, which I did withe the following code:

ad_proc -private mfa::generate_secret {} {
    Generate the secret Base32
} {
    package require base32
    
    set raw [ns_crypto::randombytes 20]
    return [base32::encode $raw]
}

# create a new secret
set secret [mfa::generate_secret]

# create URI otpauth
set issuer "OpenACS"
set account_name "Claudio@[ad_conn peeraddr]"
set uri "otpauth://totp/${issuer}:${account_name}?secret=$secret&issuer=$issuer&digits=6"

# creates QR code PNG 
set png_path /vagrant/alter-dev/www/resources/qr.png
exec qrencode -o $png_path $uri

Following is the code used to generate a TOTP based on the secret

ad_proc mfa::totp {
    -secret:required
    {-for_time ""}
    {-time_step 30}
    {-digits 6}
} {
    # generates current TOTP (6 digits, 30 seconds step)
} {
    package require base32
    
    if {$for_time eq ""} {
        set for_time [clock seconds]
    }

    set counter [expr {int($for_time / $time_step)}]
    set key [base32::decode $secret]

    return [ns_totp -key $key \
                    -time $for_time \
                    -interval $time_step \
                    -digits $digits \
                    -digest sha1]    
    
}

and finally the code to compare the generated TOTP with that generated by Google Authenticator

ad_proc mfa::verify {
    -secret:required
    -code:required
    {-time_step 30}
    {-skew 1}
    {-digits 6}
} {
    Compares the secret with the code entered by the user (skew ±1 for clock tolerance)
} {
    set code [string trim $code]
    for {set i -$skew} {$i <= $skew} {incr i} {
        set t [expr {[clock seconds] + $i * $time_step}]
	set totp_code [mfa::totp \
		 -secret    $secret \
		 -for_time  $t \
		 -time_step $time_step \
		 -digits    $digits]
        if {$totp_code eq $code} {
            return 1
        }
    }
    return 0
}

Claudio

Collapse
Posted by Gustaf Neumann on

The following snippet returns the 6-digit code:

package require base32
ns_totp -digest sha1 -digits 6 -key [base32::decode $secret]

The secret provided via the otpauth URL (typically the content of the QR code) is usually encoded in base32, for which we have no native decoder in NaviServer. I have just now tested this with a fresh security token and compared with the authenticator app, the values are identical.

Collapse
Posted by Gustaf Neumann on
I've updated the documentation of ns_totp on sourceforge to potentially reduce the search and experimentation phase

https://naviserver.sourceforge.io/n/naviserver/files/ns_totp.html