Laravel auth provider isn't working, form invalid

Need some custom code changes to the phpBB core simple enough that you feel doesn't require an extension? Then post your request here so that community members can provide some assistance.

NOTE: NO OFFICIAL SUPPORT IS PROVIDED IN THIS SUB-FORUM
Forum rules
READ: phpBB.com Board-Wide Rules and Regulations

NOTE: NO OFFICIAL SUPPORT IS PROVIDED IN THIS SUB-FORUM
User avatar
CaptainHypertext
Registered User
Posts: 25
Joined: Sun Nov 23, 2014 9:24 pm

Laravel auth provider isn't working, form invalid

Post by CaptainHypertext »

I am currently trying to join my board's authentication (phpBB 3.2.8) to a Laravel system (5.8). I downloaded tohtamysh/laravel-phpbb-bridge into my project and set everything up, and have been tweaking things to try and make it work. I would have tried opening an issue on his GIthub page, but apparently his repo doesn't allow that. At this point, it sort of works, but whenever I go to log in on the forums, it says "The submitted form was invalid. Try submitting again." I checked, and a form token is being provided. I also noticed that every time I click on the Login page, it gives me a different session id. I feel like it keeps invalidating my session on every request and is giving me a new one, so anything I post has an invalid token.

Here's the auth provider class:

Code: Select all

<?php

namespace {
    if (!defined('IN_PHPBB')) {
        exit;
    }

    define('LARAVEL_URL', 'http://example.com'); // Redacted
    define('BRIDGEBB_API_KEY', 'XXXXXXXXXXXXX'); // Redacted

    // User properties from laravel as key and phpBB as value
    define ('LARAVEL_CUSTOM_USER_DATA', serialize ([
        'id'        => 'user_id',
        'username'  => 'username',
        'email'     => 'user_email'
    ]));

    function curlResponseHeaderCallback($ch, $headerLine) {
        global $config;
        preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $headerLine, $matches);

        foreach($matches[1] as $item) {
            parse_str($item, $cookie);
            setcookie(key($cookie), $cookie[key($cookie)], time() + 86400, "/", $config['cookie_domain']);
        }

        return strlen($headerLine); // Needed by curl
    }
}

namespace laravel\bridgebb\auth\provider {
    class bridgebb extends \phpbb\auth\provider\base
    {
        public function __construct(\phpbb\db\driver\driver_interface $db)
        {
            $this->db = $db;
        }

        // Login method
        public function login($username, $password)
        {
            if (self::validate_session(['username'=>$username]) && $this->_getUserByUsername($username)) {
                return self::_success(LOGIN_SUCCESS, self::autologin());
            }

            if (is_null($password)) {
                return self::_error(LOGIN_ERROR_PASSWORD, 'NO_PASSWORD_SUPPLIED');
            }
            if (is_null($username)) {
                return self::_error(LOGIN_ERROR_USERNAME, 'LOGIN_ERROR_USERNAME');
            }

            return self::_apiValidate($username, $password);
        }

        // If user auth on laravel side but not in phpBB try to auto login
        public function autologin()
        {
            try {
                $request = self::_makeApiRequest([],'GET');
                $oResponse = json_decode($request, true);

                if (isset($oResponse['data']['username']) && isset($oResponse['code'])) {
                    if ($oResponse['code'] === '200' && $oResponse['data']['username']) {
                        $row = $this->_getUserByUsername($oResponse['data']['username']);
                        return ($row)?$row:[];
                    }
                }
                return [];
            } catch (Exception $e) {
                return [];
            }
        }

        // Validates the current session.
        public function validate_session($user_row)
        {
            if($user_row['user_id'] == ANONYMOUS)
                return false;

            try {
                $request = self::_makeApiRequest([],'GET');
                $oResponse = json_decode($request, true);

                if (isset($oResponse['data']['username']) && isset($oResponse['code'])) {
                    if ($oResponse['code'] === '200' && $oResponse['data']['username']) {
                        return ( mb_strtolower($user_row['user_id']) == mb_strtolower($oResponse['data']['id']) );
                    }
                }

                return false;
            } catch (Exception $e) {
                return false;
            }
        }

        // Logout
        public function logout($user_row, $new_session)
        {
            try {
                if (self::validate_session($user_row)) {
                    self::_makeApiRequest([],'DELETE');
                }
            } catch (Exception $e) {
            }
        }

        private function _makeApiRequest($data,$method) {
            global $request;

            $ch = curl_init();
            $cooks = '';
            $cookies = $request->get_super_global(\phpbb\request\request_interface::COOKIE);
            foreach ($cookies as $k=>$v) {
                $cooks .= $k.'='.$v.';';
            }

            $curlConfig = [
                CURLOPT_URL            => LARAVEL_URL.'/auth-bridge/login',
                CURLOPT_COOKIESESSION  => true,
                CURLOPT_COOKIE         => $cooks,
                CURLINFO_HEADER_OUT    => true,
                CURLOPT_HEADERFUNCTION => 'curlResponseHeaderCallback',
                CURLOPT_RETURNTRANSFER => true
            ];

            if ($method == 'POST') {
                $curlConfig[CURLOPT_POST] = true;
                $curlConfig[CURLOPT_POSTFIELDS] = $data;
            } elseif ($method == 'DELETE') {
                $curlConfig[CURLOPT_CUSTOMREQUEST] = 'DELETE';
            }

            curl_setopt_array($ch, $curlConfig);
            $result = curl_exec($ch);
            curl_close($ch);
            return $result;
        }

        private function _apiValidate($username, $password)
        {
            try {
                $postdata = http_build_query(
                    array(
                        'appkey' => BRIDGEBB_API_KEY,
                        'username' => $username,
                        'password' => $password
                    )
                );
                $request = self::_makeApiRequest($postdata,'POST');

                $oResponse = json_decode($request, true);
                if ($oResponse['code'] === '200') {
                    return self::_handleAuthSuccess($username, $password, $oResponse['data']);
                } else {
                    return self::_error(LOGIN_ERROR_USERNAME, 'LOGIN_ERROR_USERNAME');
                }
            } catch (Exception $e) {
                return self::_error(LOGIN_ERROR_EXTERNAL_AUTH, $e->getMessage());
            }
        }

        private function _handleAuthSuccess($username, $password, $user_laravel)
        {
            global $request;
            $server = $request->get_super_global(\phpbb\request\request_interface::SERVER);
            if ($row = $this->_getUserByUsername($username)) {
                // User inactive
                if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) {
                    return self::_error(LOGIN_ERROR_ACTIVE, 'ACTIVE_ERROR', $row);
                } else {
                    // Session hack
                    header("Location: http://" . $server['HTTP_HOST'] . $server['REQUEST_URI']);
                    die();
                    //return self::_success(LOGIN_SUCCESS, $row);
                }
            } else {
                // this is the user's first login so create an empty profile
                user_add(self::_createUserRow($username, sha1($password), $user_laravel));
                // Session hack
                header("Location: http://" . $server['HTTP_HOST'] . $server['REQUEST_URI']);
                die();
                //return self::_success(LOGIN_SUCCESS_CREATE_PROFILE, $newUser);
            }
        }

        private function _createUserRow($username, $password, $user_laravel)
        {
            global $user;
            // first retrieve default group id
            $row = $this->_getDefaultGroupID();
            if (!$row) {
                trigger_error('NO_GROUP');
            }

            // generate user account data
            $userRow = array(
                'username' => $username,
                'user_password' => phpbb_hash($password),
                'group_id' => (int) $row['group_id'],
                'user_type' => USER_NORMAL,
                'user_ip' => $user->ip,
            );

            if (LARAVEL_CUSTOM_USER_DATA && $laravel_fields = unserialize(LARAVEL_CUSTOM_USER_DATA)) {
                foreach ($laravel_fields as $key => $value) {
                    if (isset($user_laravel[$key])) {
                        $userRow[$value] = $user_laravel[$key];
                    }
                }
            }
            return $userRow;
        }

        private function _error($status, $message, $row = array('user_id' => ANONYMOUS))
        {
            return array(
                'status' => $status,
                'error_msg' => $message,
                'user_row' => $row,
            );
        }

        private function _success($status, $row)
        {
            return array(
                'status' => $status,
                'error_msg' => false,
                'user_row' => $row,
            );
        }

        private function _getUserByUsername($username)
        {
            global $db;
            $username = mb_strtolower($username);
            $sql = 'SELECT *
                FROM '.USERS_TABLE."
                WHERE LOWER(username) = '".$db->sql_escape($username)."'";
            $result = $db->sql_query($sql);
            $row = $db->sql_fetchrow($result);
            $db->sql_freeresult($result);
            return $row;
        }

        private function _getDefaultGroupID()
        {
            global $db;
            $sql = 'SELECT group_id
            FROM '.GROUPS_TABLE."
            WHERE group_name = '".$db->sql_escape('REGISTERED')."'
                AND group_type = ".GROUP_SPECIAL;
            $result = $db->sql_query($sql);
            $row = $db->sql_fetchrow($result);
            $db->sql_freeresult($result);

            return $row;
        }
    }
}
I feel like the issue could be in the validateSession method, since it just returns false if the user is anonymous? Flipping that to true allows it to work, but I have to log in to the forums twice. I just don't know enough about phpBB's session handling to be sure what is going on and how it should behave. None of the examples I saw for auth providers talked about proper implementation of session validation.

Other than this, the other functions seem to work. Autologin works when I'm logged in to my Laravel system, and logging out works.

Hoping someone with more experienced eyes can spot the issue (or any other issues). Any help would be greatly appreciated :D
User avatar
CaptainHypertext
Registered User
Posts: 25
Joined: Sun Nov 23, 2014 9:24 pm

Re: Laravel auth provider isn't working, form invalid

Post by CaptainHypertext »

Any hits? I haven't had a chance to mess with it again yet. Would rather not screw up my session handling.
User avatar
EA117
Registered User
Posts: 2173
Joined: Wed Aug 15, 2018 3:23 am

Re: Laravel auth provider isn't working, form invalid

Post by EA117 »

Someone who works on other auth providers might know more about the expectations here; I do not.

But if you were looking to "get around" the issue while you also continue to try and solve it, what comes to mind is turning off the "Tie forms to guest sessions:" under ACP General, Server Configuration, Security Settings. Since that is what's allowing the logged-out (guest) session ID to be involved in the form token being validated for the login form. Once you solve the issue to where the session ID isn't constantly changing, then you could revert to turning this back on and verifying everything still works once the session ID is involved in the login form token again.
User avatar
mrgoldy
Former Team Member
Posts: 1394
Joined: Tue Oct 06, 2009 7:34 pm
Location: The Netherlands
Name: Gijs

Re: Laravel auth provider isn't working, form invalid

Post by mrgoldy »

Well, as far as I can tell you're not really following the way phpBB has set up auth providers.
There is a collection of them, and you can choose one of them to be used in the ACP.
Here's a question from a while back, where I provided quite a bit of information in regards to the auth system: viewtopic.php?f=461&t=2502711
That is for an extension, but you should be able to do exactly the same for a core modification (custom coding).

I hope that link helps, if something is still unclear, let me now and I'll see if I can provide you with some more detailed examples.
phpBB Studio / Member of the Studio

Contributing: You can do it too! Including testing Pull Requests (PR).
phpBB Development and Testing made easy.
User avatar
CaptainHypertext
Registered User
Posts: 25
Joined: Sun Nov 23, 2014 9:24 pm

Re: Laravel auth provider isn't working, form invalid

Post by CaptainHypertext »

Thanks for the replies!

Sorry I wasn't more detailed. This is indeed an auth provider extension that I enabled, and switched my authentication method to in the ACP. It is pretty much tohtamysh/laravel-phpbb-bridge with some tweaks. The file I posted is /ext/laravel/bridgebb/auth/provider/bridgebb.php.

I guess I would ask if validate_session is properly handling anonymous sessions? In the post you linked, I saw their validate_session function referenced the USER_IGNORE user type. My theory is that this line is my problem:

Code: Select all

 public function validate_session($user_row)
        {
            if($user_row['user_id'] == ANONYMOUS)
                return false;
            
            ...
Since it always invalidates the anonymous session. But then when you log in, it doesn't invalidate the anonymous session. Or, it doesn't appear to. Maybe I'm just chasing a goose here...
User avatar
CaptainHypertext
Registered User
Posts: 25
Joined: Sun Nov 23, 2014 9:24 pm

Re: Laravel auth provider isn't working, form invalid

Post by CaptainHypertext »

I think I actually am making some progress here. I looked through the Apache auth provider for reference, and changed the above code block to this:

Code: Select all

public function validate_session($user_row)
        {
            if($user_row['user_type'] == USER_IGNORE)
                return true;
            
            ...
Then I noticed this and switched the comments out. Not sure why this was here:

Code: Select all

// Session hack
header("Location: http://" . $server['HTTP_HOST'] . $server['REQUEST_URI']);
die();
//return self::_success(LOGIN_SUCCESS_CREATE_PROFILE, $newUser);
And at this point login doesn't work because, even though it logs into Laravel over the api, it's not delivering the cookies it gets from Laravel back to the client... which seems like a fundamental flaw. Because then you're not logged in to the Laravel app, but you're logged in to the forums, which immediately gets invalidated because your Laravel session is a dud.

Thanks again, I'll continue to document this here for future travelers.

Return to “phpBB Custom Coding”