XF 2.0 Where to start to add a fully custom logon option?

Steve Gibson

Active member
Hello XenForo developers,

I am in the process of bringing up a new set of forums for the discussion and development of a revolutionary Internet/Web user authentication system (known as SQRL) which can completely replace traditional username & password logon identity authentication.

Given that the forum is all about this new logon solution, the forums need to support this system, too. So I'm writing to ask those who wrote XenForo where I should start looking for the hooks to add another completely custom logon?

Any pointers to functions, hooks, possible documentation or other useful support will be MUCH appreciated and would likely keep me from doing it the wrong way.

Thanks VERY much! :)

/Steve Gibson. (GRC.COM)
 
I don't know much about creating add-ons but I do know that Xenforo 2 does not use hooks like Xenforo 1 did. They replaced it with something called the finder system. You can read more about it in the Xenforo 2 developer manual. I highly suggest dedicating some time to reading it. It has an example add-on you can create called "Let's build an add-on" where you create a CMS (content management system) add-on that can feature threads

More on the finder here:

https://xenforo.com/xf2-docs/dev/entities-finders-repositories/

Developer docs homepage:

https://xenforo.com/xf2-docs/dev/
 
Thanks, Brad!

I will begin reading those docs while, hopefully, hearing from one of the XenForo authors about how they would do it if it was up to them.

I doubt that my need to add a fully custom user authentication system falls under the category of a XenForo "add-on" -- but I'm just getting my feet wet here. :)

Thanks again!
 
Hey Steve,

Huge fan of Security Now! I have listened to all the episodes and am well aware what you are working on. I smiled when I found out you chose to use XenForo for your SQRL boards. Would be fun to work on this project together.

I've been making add-ons for XenForo 1.0 and until some months ago started to get my feet wet with XenForo 2.0. I think it's safe to say that adding SQRL support to XenForo could be classified as an addon. in XenForo, you can replace any behavior with your own without editing a single line of the vanilla code. Editing the PHP files of XenForo is frowned upon and considered dirty. It's hard to stay up to date with software updates if you have to manually re-apply your updates on every new release. Instead you can modify the PHP classes indirectly with addons. Basically your code is applied to the existing code-base on the fly in a safe and managed way. I am sure you will like the design. When I switched to XenForo from vBulletin I came to appreciate how much this system shines. It was built by developers for developers.

Now, in order to implement SQRL into XenForo I would probably integrate with the 'Connected accounts' system of XenForo. It allows you to register based on a 'connected account'. I will tinker with this to see if we can use it to present a SQRL QR code and authenticate in the way SQRL does. I believe we can make a way to clear the user password hash to prevent this login method entirely (per user).

<Some time later>

I have looked a bit into this and started dabbling a bit in how I would do this. I have looked into it and it's quite simple to integrate with the 'Connected accounts' system. See image below:
Screen Shot 2018-08-10 at 16.54.44.webp
I don't want to create my own SQRL implementation, so I am assuming the existence of this PHP library:
https://github.com/trianglman/sqrl, it requires PHP 7.2 which is a bit of a pain, but I found a way to install it on my server.

I created a new addon:
Code:
php /www/cmd.php xf-addon:create

The file www/src/addons/SQRL/Setup.php is your script to install/uninstall the addon, you can make it create your 'connected account' by having it execute this query:
Code:
        INSERT INTO `xf_connected_account_provider` (`provider_id`, `provider_class`, `display_order`, `options`)
        VALUES
            (X'sqrl', 'SQRL:Provider\\SQRL', 80, X'');
For now I just ran this query manually. Making the Setup.php do it is polishing for later.

This tells XenForo that we have our own new connected account system. It tells XF that the file www/src/addons/SQRL/ConnectedAccount/Provider handles the behavior of our system. I create this file:
PHP:
<?php

namespace SQRL\ConnectedAccount\Provider;

use XF\ConnectedAccount\Http\HttpResponseException;
use XF\ConnectedAccount\Provider\AbstractProvider;
use XF\Entity\ConnectedAccountProvider;
use XF\Mvc\Controller;
use SQRL\SqrlStore;

class SQRL extends AbstractProvider
{
    public function getOAuthServiceName()
    {
        return 'SQRL';
    }

    public function getDefaultOptions()
    {
        return ['key_domain' => ''];
    }

    public function getOAuthConfig(ConnectedAccountProvider $provider, $redirectUri = null)
    {
        // We just want a link to our QR page
        return [
            'key' => '',
            'secret' => '',
            'scopes' => '',
            'redirect' => \XF::app()->router()->buildLink('sqrl/authenticate'),
        ];
    }

    // We override this because AbstractProvider assumes everyone is using OAuth, for custom behavior we need something like this instead
    public function handleAuthorization(Controller $controller, ConnectedAccountProvider $provider, $returnUrl)
    {
        \SQRL\RegisterDependencies::startAutoloader();

        $config = new \Trianglman\Sqrl\SqrlConfiguration();

        //whether SQRL responses should come back over SSL (sqrl://)
        $config->setSecure(true);
        //the domain sqrl clients should generate their key off of
        $config->setDomain($provider->options['key_domain']);
        //the path to the SQRL authentication script relative to the key domain
        $config->setAuthenticationPath($controller->router()->buildLink('sqrl/login'));

        // Run SQRL code to get a QR code
        $generator = new \Trianglman\Sqrl\SqrlGenerate($config, new SqrlStore());

        $tempFile = \XF\Util\File::getTempFile();
        $generator->render($tempFile);
        $dataUrl = 'data:' . 'image/jpeg' . ';base64,' . base64_encode(file_get_contents($tempFile));
        $sqrlUrl = $generator->getUrl();

        $viewParams = [
            'dataUrl' => $dataUrl,
            'sqrlUrl' => $sqrlUrl,
        ];
        return $controller->view('SQRL\ViewQR', 'sqrl_view_qr_code', $viewParams);
    }
}

It also assumes the existence of a admin template called 'connected_account_provider_sqrl.html'. I made this here:
Code:
<xf:textboxrow name="options[key_domain]" value="{$options.key_domain}"
    label="{{ phrase('domain') }}"
    hint="{{ phrase('required') }}"
    explain="{{ phrase('con_acc_sqrl_key_domain_explain') }}" />
To explicitly tell SQRL which domain we are authenticating with.

Now, when I go here, I see the SQRL button:
Screen Shot 2018-08-10 at 17.07.22.webp
If you look at the method named 'handleAuthorization' in the class before, you see I am overriding the existing OAuth behavior of the parent class to make it present a template with a SQRL QR code. The template 'sqrl_view_qr_code' has the following contents:
HTML:
<xf:title>{{ phrase('authenticate_using_sqrl') }}</xf:title>

<a href="{$sqrlUrl}"><img src="{$dataUrl}" /></a>

Screen Shot 2018-08-10 at 17.33.43.webp

Now, in order to actually make the user authenticate, we need a URL with which they can use to send their information. I created the file:
/www/src/addons/SQRL/Pub/Controller/SQRL.php:
PHP:
<?php

namespace SQRL\Pub\Controller;

use XF\Mvc\ParameterBag;
use XF\Pub\Controller\AbstractController;

class SQRL extends AbstractController
{
    public function actionLogin(ParameterBag $params)
    {
        \SQRL\RegisterDependencies::startAutoloader();

        $provider = $this->finder('XF:ConnectedAccountProvider')->where('provider_id', '=', 'sqrl')->fetchOne();
        $config = new \Trianglman\Sqrl\SqrlConfiguration();
        //whether SQRL responses should come back over SSL (sqrl://)
        $config->setSecure(true);
        //the domain sqrl clients should generate their key off of
        $config->setDomain($provider->options['key_domain']);
        //the path to the SQRL authentication script relative to the key domain
        $config->setAuthenticationPath($this->router()->buildLink('sqrl/login'));

        $store = new \SQRL\SqrlStore();

        //initialize the validator
        $validator = new \Trianglman\Sqrl\SqrlValidate(
            $config,
            new \Trianglman\Sqrl\SodiumNonceValidator(),
            $store,
        );

        $requestHandler = new \Trianglman\Sqrl\SqrlRequestHandler(
            $config,
            $validator,
            null,
            new \Trianglman\Sqrl\SqrlGenerate($config, $store),
        );
        $requestHandler->parseRequest($_GET, $_POST, $_SERVER);
        $responseMessage = $requestHandler->getResponseMessage();
        exit($responseMessage);
    }
}
And let XenForo know about it by going to Admin -> Development -> Routes -> Add route: public with the values:
Route type: public
Route prefix: sqrl
Controller: SQRL:SQRL (this is short for \SQRL\Pub\Controller\SQRL because it is context aware)
Add-on: SQRL

Now we can handle SQRL challenges and return the base64 payload but I have not actually attached it to further registration, user creation, user login but it's definitely doable.

This is a huge amount of information all at once and not fully in-depth. There's a lot to learn if you're doing it all yourself and want to do everything the XenForo way which you should :).

The code I've shown here is super drafty and I just wanted to throw some things together to see if I could get it to work. I would love to work with you on this for real. SQRL sounds like a good system.
 
Last edited:
Ralle...

Holy Crap!! Thank you thank you thank you!

This/you will be a HUGE help, and your timing is PERFECT.

I cannot write more this instant, but I will later today!

THANK YOU for your offer of help/collaboration!
 
Ralle...

I have deliberately delayed my full reply to you until I can get everything said that I want to. That should be tomorrow (Sunday).

In the mean time, there IS some fun stuff you can see of what I've done so far.

Go to: https://sqrl.grc.com and examine the Login and Register forms. <g> This was done, as you correctly suggested, as a formal and proper XenForo add-on. As we both know, that's the only way to surviec XenForo updates. (It recently survived the jump from XenForo v2.0.7 to 2.0.9.) If you refresh either of those pages and hover your pointer over the SQRL button, you'll see that the URL changes with each refresh... and that the QR code does also (more on that later).

Also, you should grab a copy of my latest pre-release (but completely ready for the world) SQRL client for Windows. If you are a Mac or Linux person, it will run under WINE. The SQRL client is here: https://www.grc.com/dev/sqrl.exe and it will automatically (with your permission each time) notify you of any available updates.

After you get the client configured (you just run it and it will guide you through the process), you can play with authentication using the SQRL test server and demo, here: https://www.grc.com/sqrl/demo.htm

And lastly, my direct eMail is: steve@grc.com which might be better for our ongoing interactions than using this forum.

I will read and respond to your original note, point by point, tomorrow (Sunday). Until then... thanks SO MUCH again! And all the best.
 
Last edited by a moderator:
Ralle,

Huge fan of Security Now! I have listened to all the episodes and am well aware what you are working on. I smiled when I found out you chose to use XenForo for your SQRL boards. Would be fun to work on this project together.

I've been making add-ons for XenForo 1.0 and until some months ago started to get my feet wet with XenForo 2.0. I think it's safe to say that adding SQRL support to XenForo could be classified as an addon. in XenForo, you can replace any behavior with your own without editing a single line of the vanilla code. Editing the PHP files of XenForo is frowned upon and considered dirty. It's hard to stay up to date with software updates if you have to manually re-apply your updates on every new release. Instead you can modify the PHP classes indirectly with addons. Basically your code is applied to the existing code-base on the fly in a safe and managed way. I am sure you will like the design. When I switched to XenForo from vBulletin I came to appreciate how much this system shines. It was built by developers for developers.

I thank you again for your interest in helping with this project. Literally the only thing holding up my release of SQRL is that there's no way for SQRL's own public-access web forums to NOT support its own new authentication mechanism. :eek:

You'll also now know from my previous post that the work I've done so far has been to implement a XenForo AddOn... for all the reasons you noted.

And I agree 100% with your appraisal of XenForo's quality as a web forum foundation. I am very very pleased with my choice. As a developer it's clear that the design is the result of its authors having previously designed several other systems. It shows a maturity and coherence that's a pleasure to work with.

Now, in order to implement SQRL into XenForo I would probably integrate with the 'Connected accounts' system of XenForo. It allows you to register based on a 'connected account'. I will tinker with this to see if we can use it to present a SQRL QR code and authenticate in the way SQRL does. I believe we can make a way to clear the user password hash to prevent this login method entirely (per user).

I have come to the same conclusion, Ralle. I have previously examined the existing posts/questions from others who want to integrate other authentication systems and the XF devs point to extending the existing Connected Accounts system.

<Some time later>

I have looked a bit into this and started dabbling a bit in how I would do this. I have looked into it and it's quite simple to integrate with the 'Connected accounts' system. See image below:
View attachment 181318
I don't want to create my own SQRL implementation, so I am assuming the existence of this PHP library:
https://github.com/trianglman/sqrl, it requires PHP 7.2 which is a bit of a pain, but I found a way to install it on my server.

I created a new addon:
Code:
php /www/cmd.php xf-addon:create

The file www/src/addons/SQRL/Setup.php is your script to install/uninstall the addon, you can make it create your 'connected account' by having it execute this query:
Code:
        INSERT INTO `xf_connected_account_provider` (`provider_id`, `provider_class`, `display_order`, `options`)
        VALUES
            (X'sqrl', 'SQRL:Provider\\SQRL', 80, X'');
For now I just ran this query manually. Making the Setup.php do it is polishing for later.

This tells XenForo that we have our own new connected account system. It tells XF that the file www/src/addons/SQRL/ConnectedAccount/Provider handles the behavior of our system. I create this file:
PHP:
<?php

namespace SQRL\ConnectedAccount\Provider;

use XF\ConnectedAccount\Http\HttpResponseException;
use XF\ConnectedAccount\Provider\AbstractProvider;
use XF\Entity\ConnectedAccountProvider;
use XF\Mvc\Controller;
use SQRL\SqrlStore;

class SQRL extends AbstractProvider
{
    public function getOAuthServiceName()
    {
        return 'SQRL';
    }

    public function getDefaultOptions()
    {
        return ['key_domain' => ''];
    }

    public function getOAuthConfig(ConnectedAccountProvider $provider, $redirectUri = null)
    {
        // We just want a link to our QR page
        return [
            'key' => '',
            'secret' => '',
            'scopes' => '',
            'redirect' => \XF::app()->router()->buildLink('sqrl/authenticate'),
        ];
    }

    // We override this because AbstractProvider assumes everyone is using OAuth, for custom behavior we need something like this instead
    public function handleAuthorization(Controller $controller, ConnectedAccountProvider $provider, $returnUrl)
    {
        \SQRL\RegisterDependencies::startAutoloader();

        $config = new \Trianglman\Sqrl\SqrlConfiguration();

        //whether SQRL responses should come back over SSL (sqrl://)
        $config->setSecure(true);
        //the domain sqrl clients should generate their key off of
        $config->setDomain($provider->options['key_domain']);
        //the path to the SQRL authentication script relative to the key domain
        $config->setAuthenticationPath($controller->router()->buildLink('sqrl/login'));

        // Run SQRL code to get a QR code
        $generator = new \Trianglman\Sqrl\SqrlGenerate($config, new SqrlStore());

        $tempFile = \XF\Util\File::getTempFile();
        $generator->render($tempFile);
        $dataUrl = 'data:' . 'image/jpeg' . ';base64,' . base64_encode(file_get_contents($tempFile));
        $sqrlUrl = $generator->getUrl();

        $viewParams = [
            'dataUrl' => $dataUrl,
            'sqrlUrl' => $sqrlUrl,
        ];
        return $controller->view('SQRL\ViewQR', 'sqrl_view_qr_code', $viewParams);
    }
}

It also assumes the existence of a admin template called 'connected_account_provider_sqrl.html'. I made this here:
Code:
<xf:textboxrow name="options[key_domain]" value="{$options.key_domain}"
    label="{{ phrase('domain') }}"
    hint="{{ phrase('required') }}"
    explain="{{ phrase('con_acc_sqrl_key_domain_explain') }}" />
To explicitly tell SQRL which domain we are authenticating with.

Now, when I go here, I see the SQRL button:
View attachment 181321
If you look at the method named 'handleAuthorization' in the class before, you see I am overriding the existing OAuth behavior of the parent class to make it present a template with a SQRL QR code. The template 'sqrl_view_qr_code' has the following contents:
HTML:
<xf:title>{{ phrase('authenticate_using_sqrl') }}</xf:title>

<a href="{$sqrlUrl}"><img src="{$dataUrl}" /></a>

View attachment 181325

Now, in order to actually make the user authenticate, we need a URL with which they can use to send their information. I created the file:
/www/src/addons/SQRL/Pub/Controller/SQRL.php:
PHP:
<?php

namespace SQRL\Pub\Controller;

use XF\Mvc\ParameterBag;
use XF\Pub\Controller\AbstractController;

class SQRL extends AbstractController
{
    public function actionLogin(ParameterBag $params)
    {
        \SQRL\RegisterDependencies::startAutoloader();

        $provider = $this->finder('XF:ConnectedAccountProvider')->where('provider_id', '=', 'sqrl')->fetchOne();
        $config = new \Trianglman\Sqrl\SqrlConfiguration();
        //whether SQRL responses should come back over SSL (sqrl://)
        $config->setSecure(true);
        //the domain sqrl clients should generate their key off of
        $config->setDomain($provider->options['key_domain']);
        //the path to the SQRL authentication script relative to the key domain
        $config->setAuthenticationPath($this->router()->buildLink('sqrl/login'));

        $store = new \SQRL\SqrlStore();

        //initialize the validator
        $validator = new \Trianglman\Sqrl\SqrlValidate(
            $config,
            new \Trianglman\Sqrl\SodiumNonceValidator(),
            $store,
        );

        $requestHandler = new \Trianglman\Sqrl\SqrlRequestHandler(
            $config,
            $validator,
            null,
            new \Trianglman\Sqrl\SqrlGenerate($config, $store),
        );
        $requestHandler->parseRequest($_GET, $_POST, $_SERVER);
        $responseMessage = $requestHandler->getResponseMessage();
        exit($responseMessage);
    }
}
And let XenForo know about it by going to Admin -> Development -> Routes -> Add route: public with the values:
Route type: public
Route prefix: sqrl
Controller: SQRL:SQRL (this is short for \SQRL\Pub\Controller\SQRL because it is context aware)
Add-on: SQRL

Now we can handle SQRL challenges and return the base64 payload but I have not actually attached it to further registration, user creation, user login but it's definitely doable.

This is a huge amount of information all at once and not fully in-depth. There's a lot to learn if you're doing it all yourself and want to do everything the XenForo way which you should :).

The code I've shown here is super drafty and I just wanted to throw some things together to see if I could get it to work. I would love to work with you on this for real. SQRL sounds like a good system.

Holy crap, Ralle! You did a LOT of work on this! I'm going to write another posting to provide some orientation about what I've been thinking. More shortly...
 
Continuing, Ralle...

I'm not a PHP programmer, though, like most people who have coded in many languages, writing a small bit of code (or even eventually a large amount) is mostly a matter of learning the specifics of the target language and keeping a language reference handy. However... writing ==SECURE== code in any not-well-understood language is another thing entirely.

Also, I have existing very mature SQRL server-side code running at GRC (written in MASM, of course! <g>)

And I'm a bit of a perfectionist. Little things that could be better, but aren't, annoy me. In this case, the SQRL nonce (which we call a nut) only needs to be long enough to prevent guessing. It needs to be one-time-only and guaranteed never to repeat. And size of a QR code is not linearly related to the size of its content... it jumps in size. So, being me, I chose the SQRL nut length which was both long enough to be secure, and also the longest that could be encoded in a given size QR code before it "jumps" to a larger size. The PHP code is (needlessly) producing an insanely long nut, which produces an unnecessarily large QR code. Yeah, I know, it doesn't matter... but it does to me.

Also, [I just looked and] it doesn't appear as though that PHP library is yet a full implementation of the SQRL server-side protocol. It appears to be lacking a number of crucial features, such as support for SQRL's identity lock protocol. Naturally, GRC's use of SQRL needs to be complete.

So my plan for the SQRL forums has been to keep nearly all of the heavy lifting over in MASM where I am not only more comfortable than anywhere else, but also where I already have well pounded on server-side code. This is going to be a minimal ISAPI module which anyone will be able to use. As you many know, ISAPI is natively Microsoft/IIS, but it is also supported by Apache and a few other lesser known servers.

(Are you able to run an ISAPI module? If so, that would allow you to have a running instance of what I'm doing. Though, if not, since I'm planning to implement this as an HTTP query/response interface... i.e. as a RESTful API service... we could probably arrange for remote access to GRC's API.)

(And also note that having a simple and clean SQRL RESTful API service would be useful all on its own. :)

At the moment I have the following server-side functions supported, which you can test and experiment with for yourself:

This URL returns a guaranteed globally unique SQRL nut nonce: https://sqrl.grc.com/sqrl/nut.txt

This URL returns a QR code containing a SQRL auth trigger: https://sqrl.grc.com/sqrl?qr=xitltrdtWh54rDAuq630RC0.png

(Where 'xitltrdtWh54rDAuq630RC0' is one of the nut nonces, for example returned by the query to /nut.txt)

And though they don't yet do anything, these URLs will soon:

... trigger a SQRL authentication:

https://sqrl.grc.com/sqrl?nut=xitltrdtWh54rDAuq630RC0

... return a value that changes when the page should be updated to show the user's logged in state:

https://sqrl.grc.com/sqrl/sync.txt

________________________________________________________________

I'm very much a one step at a time sort of guy, so I haven't yet allowed myself to think any further about the XenForo integration since I've known I would work it out once I get there. That said...

But a SQRL user is identified by a 256-bit public key which is base64url-encoded for interchange. And their SQRL identity on the server also requires the server to store another pair of 256-bit values for the so-called "Identity Lock" which supports a number of cool and unique features.

SQRL provides a means for the user to securely "re-key" their identity using a SQRL client. This might be needed if, for any reason, they were to lose confidence in the confidentiality of their current SQRL identity. From our standpoint with XenForo, this means that a given user's 256-bit SQRL identity might change at any time... yet they need to remain the same user to XenForo. So the architectural question is: Does XenForo identity them by their 256-bit SQRL identity (in the Connected Identities sense) -- which we =would= need to be able to change for their account in the event of re-keying?... OR should their SQRL identity be retained in the ISAPI database where it would be linked to their permanent XenForo account ID? I'm fine with it either way, but storing their XenForo identity in the ISAPI database would mean that we don't need to edit the XenForo database.

I also have not yet faced the question of step-by-step XenForo-side activities... but I =do= think that it probably (and wonderfully) very closely mimics the existing flow of OAuth Connected Accounts support... although, again, I haven't yet looked into that deeply.
________________________________________________________________

Now that I've got the "Login with SQRL" button and QR code stuff running and inplace (which required a bunch of infrastructure) next up for me is to port the rest of GRC's existing SQRL server-side support over into the new SQRL ISAPI module. (I forgot to mention that at the moment GRC's SQRL code is hung onto the existing monolithic "NetEngine" ISAPI which runs all of my server extensions, ShieldsUP!, DNS Spoofability, eCommerce, etc. So I'm now creating a stand-alone SQRL ISAPI for the forums (which runs on a physically separate and isolated server).

I will make time to digest the work you have done, but I first wanted to explain my thinking about where I am and where I think we're headed!

Thanks again for your help and support, Ralle!
 
A possible division of labor, if that makes sense to you:

Ralle...

It might make the most sense if I provide a full SQRL support RESTful API, and you handle the XenForo integration side?

I'll of course give you the SQRL addon I've built so far, and I can/will also add to the existing JavaScript support that needs to run on the login/registration pages to support SQRL's CPS (Client Provided Session) and optical QR code "auto page update" system. (You can see it on GRC's SQRL demo pages.)

Anyway... just a thought, since I know SQRL and you already know XenForo (and PHP) far better than I do. :)
 
Ralle,

Terrific!, thanks. I just told the gang over in GRC's SQRL newsgroup about you and summarized our dialog so far. GRC's newsgroups really are amazing, and if there's any way for you to setup an old-school NNTP-style news reader (I'm using Microplanet Gravity v2.70 on Windows but Mozilla's Thunderbird also has NNTP newsreading capabilities) ... I think you'll find it's worth doing. But that's certainly not necessary if you have other priorities. :)

All the best.
 
Back
Top Bottom