XF 2.0 External logout

LPH

Well-known member
External login, link to the profile, etc seems to be working but the external logout link returns a message stating:

Security error occurred. Please press back, refresh the page, and try again.

Code:
<?php echo '<a class="xenword_logout logout" href="' . \XF::app()->router()->buildLink( 'canonical:account' ) . '">Profile</a>'; ?>
<?php echo '<a class="xenword_logout logout" href="' . \XF::app()->router()->buildLink( 'canonical:logout', $visitor, array(
      '_xfToken' => $visitor['csrf_token_page'],
      'redirect' => $redirect_out
   ) ) . '">Logout</a>'; ?>

I'm thinking it is the $visitor array but maybe there is a better way to do this rather than the above code.

Update: htmlspecialchars($this->app['csrf.token']) is the value for _xfToken but I'm not calling it right.

Code:
<?php echo '<a class="xenword_logout logout" href="' . \XF::app()->router()->buildLink( 'canonical:logout', $visitor, array(
      '_xfToken' => htmlspecialchars(\XF::app()->app['csrf.token']),
      'redirect' => $redirect_out
   ) ) . '">Logout</a>'; ?>

It's getting a container key app error.

'_xfToken' => htmlspecialchars(\XF::app()->container( 'csrf.token' ) ),

But stuck on the same error.

Thank you for your help.
 
Last edited:
Almost. This will log out the user but not redirect properly.

Code:
<?php echo '<a class="xenword_logout logout" href="' . \XF::app()->router()->buildLink( 'canonical:logout', $visitor, array(
      't' => htmlspecialchars(\XF::app()->container( 'csrf.token' ) ),
      'redirect' => $redirect_out
   ) ) . '">Logout</a>'; ?>
 
Sorry to necro an old thread, but this is the first one that comes up via google, and I faced exactly the same problem. I decided to solve this using a custom addon, with a public endpoint and an API endpoint.

API Endpoint:
This generates us a link to allow the user to be redirected to a logout screen. It generates a token, using their existing xf_csrf cookie, or generates a new one for them.

File:
/src/addons/{CompanyName}/{AddonName}/Api/Controller
PHP:
namespace Andersoft\SsoAddon\Api\Controller;

use XF\Api\Controller\AbstractController;

/**
 * @api-group Auth
 */
class Auth extends AbstractController
{
    public function actionPostLogoutLink()
    {
        $token = $this->filter("csrf_token", "str");
        $redirect = $this->filter("redirect", "str");

        // Get a new token if none set
        if (!$token) {
            $token = \XF::generateRandomString(16);
        }

        /** @var \Closure $validator */
        $validator = \XF::app()->container('csrf.validator');
        $fullToken =  \XF::$time . ',' . $validator($token, \XF::$time);

        // Generate a logout link for the user
        $publicRouter = $this->app->router('public');
        $link = $publicRouter->buildLink('canonical:logout', null, array(
            't' => htmlspecialchars($fullToken)
        ));

        $customLink = $publicRouter->buildLink('canonical:andersoft/external-logout', null, array(
            't' => htmlspecialchars($fullToken),
            'redirect' => $redirect ?? \XF::app()->container("homePageUrl")
        ));

        return $this->apiSuccess([
            'token' => $token,
            'custom_logout' => $customLink,
            'link' => $link
        ]);
    }
}

Register this endpoint in the admin, an example of my configuration is below:

JSON:
{
    "route_type": "api",
    "route_prefix": "andersoft",
    "sub_name": "",
    "format": "",
    "build_class": "",
    "build_method": "",
    "controller": "Andersoft\\SsoAddon:Auth",
    "context": "",
    "action_prefix": ""
}

Next, create a public endpoint to actually handle the logout:

File:
/src/addons/{CompanyName}/{AddonName}/Pub/Controller
PHP:
namespace Andersoft\SsoAddon\Pub\Controller;

use XF\Api\Controller\AbstractController;

/**
 * @api-group Auth
 */
class Auth extends AbstractController
{
    public function actionExternalLogout()
    {
        $this->assertValidCsrfToken($this->filter('t', 'str'));

        /** @var \XF\ControllerPlugin\Login $loginPlugin */
        $loginPlugin = $this->plugin('XF:Login');
        $loginPlugin->logoutVisitor();

        $redirectTo = $this->filter('redirect', 'str');
        if (!$redirectTo) {
            $redirectTo = \XF::app()->homePageUrl();
        }

        return $this->redirect($redirectTo);
    }
}

And once again, register this one:
JSON:
{
    "route_type": "public",
    "route_prefix": "andersoft",
    "sub_name": "",
    "format": "",
    "build_class": "",
    "build_method": "",
    "controller": "Andersoft\\SsoAddon:Auth",
    "context": "",
    "action_prefix": ""
}

Within your external application, you can now call /api/andersoft/logout-link and it will return you the link and a token. If the token does not exist as a session cookie, create it. Redirect the user to the public API.
 
Sorry to necro an old thread, but this is the first one that comes up via google, and I faced exactly the same problem. I decided to solve this using a custom addon, with a public endpoint and an API endpoint.

API Endpoint:
This generates us a link to allow the user to be redirected to a logout screen. It generates a token, using their existing xf_csrf cookie, or generates a new one for them.

File:

PHP:
namespace Andersoft\SsoAddon\Api\Controller;

use XF\Api\Controller\AbstractController;

/**
 * @api-group Auth
 */
class Auth extends AbstractController
{
    public function actionPostLogoutLink()
    {
        $token = $this->filter("csrf_token", "str");
        $redirect = $this->filter("redirect", "str");

        // Get a new token if none set
        if (!$token) {
            $token = \XF::generateRandomString(16);
        }

        /** @var \Closure $validator */
        $validator = \XF::app()->container('csrf.validator');
        $fullToken =  \XF::$time . ',' . $validator($token, \XF::$time);

        // Generate a logout link for the user
        $publicRouter = $this->app->router('public');
        $link = $publicRouter->buildLink('canonical:logout', null, array(
            't' => htmlspecialchars($fullToken)
        ));

        $customLink = $publicRouter->buildLink('canonical:andersoft/external-logout', null, array(
            't' => htmlspecialchars($fullToken),
            'redirect' => $redirect ?? \XF::app()->container("homePageUrl")
        ));

        return $this->apiSuccess([
            'token' => $token,
            'custom_logout' => $customLink,
            'link' => $link
        ]);
    }
}

Register this endpoint in the admin, an example of my configuration is below:

JSON:
{
    "route_type": "api",
    "route_prefix": "andersoft",
    "sub_name": "",
    "format": "",
    "build_class": "",
    "build_method": "",
    "controller": "Andersoft\\SsoAddon:Auth",
    "context": "",
    "action_prefix": ""
}

Next, create a public endpoint to actually handle the logout:

File:

PHP:
namespace Andersoft\SsoAddon\Pub\Controller;

use XF\Api\Controller\AbstractController;

/**
 * @api-group Auth
 */
class Auth extends AbstractController
{
    public function actionExternalLogout()
    {
        $this->assertValidCsrfToken($this->filter('t', 'str'));

        /** @var \XF\ControllerPlugin\Login $loginPlugin */
        $loginPlugin = $this->plugin('XF:Login');
        $loginPlugin->logoutVisitor();

        $redirectTo = $this->filter('redirect', 'str');
        if (!$redirectTo) {
            $redirectTo = \XF::app()->homePageUrl();
        }

        return $this->redirect($redirectTo);
    }
}

And once again, register this one:
JSON:
{
    "route_type": "public",
    "route_prefix": "andersoft",
    "sub_name": "",
    "format": "",
    "build_class": "",
    "build_method": "",
    "controller": "Andersoft\\SsoAddon:Auth",
    "context": "",
    "action_prefix": ""
}

Within your external application, you can now call /api/andersoft/logout-link and it will return you the link and a token. If the token does not exist as a session cookie, create it. Redirect the user to the public API.
Would you be willing to release this code as an addon? I'd love to make my own integration compatible with it.
 
Would you be willing to release this code as an addon? I'd love to make my own integration compatible with it.
Certainly, I was going to write an article / blog post about it, specifically for integration with Laravel, but I can certainly publish an addon that could work with other frameworks.
 
Top Bottom