Authenticate against 3rd Party User Database

I want to give some feedback and a possible solution:

To provide a frontend only login (members area, personalized content like “Hello Name”) with credentials from a third party application (be it an api or external database) one has a few options:

  1. DIY :arrow_right: authenticate against the third party and save the logged in status in a session and check that. Downside is, can’t use the Kirby->user() object

  2. Add Users Programmatically :arrow_right: authenticate against the third party and create a new User to Kirby and loginPasswordless() (this helped me)

  3. Hybrid Temporary/Kirby Users :arrow_right: my final approach is inspired by @texnixe load users from database receipt that’s based on this forum post and with some hints from GitHub - thathoff/kirby-oauth: OAuth 2 Login for Kirby 3.

Hybrid Temporary/Kirby Users

The goal was to authenticate against a third party members collection[1] and not pollute Kirby’s user implementation (adding a ton of frontend-only users and potentially degrade performance).

  1. Authenticate the user on 3rd party app
  2. Create a new User object (User::factory)
  3. Add the userdata to the kirby()->users() collection
  4. Save the data of this new User to session()->set('member', [...])
  5. loginPasswordless() (sets kirby.userId to find the user in collection) and redirect to the member page
  6. new CustomKirby on boot checks if session()->get('member') has a user and merges it into the Kirby Users collection.
  7. Use kirby()->user()

At runtime there are all real Kirby users available plus one temporary user in the session.

Authentication

// do some api calls, to get the user data
$userdata = $this->request($url, $params)->json();

$user = \User::factory([
    'name'      => A::get($userdata, 'email'),
    'email'     => A::get($userdata, 'email'),
    //'id'        => A::get($userdata,'id'), // might set my own unique id from API
    'password'  => bin2hex(random_bytes(32)),
    'language'  => A::get($userdata, 'language', 'de'),
    'role'      => 'visitor'
]);

$users = kirby()->users();

$users->add($user);

// add user to session and load it in `new CustomKirby`
kirby()->session()->set('member', $user->toArray());

kirby()->user(A::get($userdata, 'email'))->loginPasswordless();

go('/hidden-members-page');

Overload Users

As described here, it’s possible to overload the way, how Kirby loads the Users collection.

<?php

use Kirby\Cms\Users;

/**
 * Overload users method
 * and merge users temporarily from a third party login
 *
 */
class CustomKirby extends Kirby
{

	/**
	 * Returns all users
	 *
	 * @see \kirby\src\cms\AppUsers.php:134
	 * @return \Kirby\Cms\Users
	 */
	public function users()
	{
		if (is_a($this->users, 'Kirby\Cms\Users') === true) {
			return $this->users;
		}

		$this->users = Users::load($this->root('accounts'), ['kirby' => $this]);

		if ($this->session()->get('member')) {

			if ($this->users) {
				$users = $this->users->toArray();
			}

			$users[] = $this->session()->get('member');

			return $this->users = Users::Factory($users, ['kirby' => $this]);
		}

		return $this->users;
	}
}

I’m kindly asking for feedback and potentially pitfalls of this solution (conflicts with ids, existing users, sessions, data leaks)

Thank you!

PS: I love kirby!

[1]: In my case I’m working with a members table administered by the Directus headless cms.