Authenticate against 3rd Party User Database

Hi,

I’ve got a database with users (username+password) that I can access via an API and I want users to be able to login into a member area via the frontend and be authenticated against that database.

Is it possible to bypass the Kirby Frontend Auth as described here:

Frontend login | Kirby CMS
Restricting access to your site | Kirby CMS

or maybe with this plugin: https://github.com/kerli81/kirby-securedpages ?

If the member area is on the frontend, your authentication doesn’t even need users and you can apply your custom logic anyway. So basically you would POST the data to the database, check if it fits and then store this in the session. Downside is that you cannot take advantage of all Kirby user related method (checking for existing user, log in etc.)

Alternatively, you could also turn your users from your database into virtual users without Panel access.

I’ve written a recipe PR here: https://github.com/getkirby/getkirby.com/pull/1421 (and if you don’t want to edit such users but only need read access, you only need the first part, with some modifications with a separate user type without Panel access.

As far as I can tell that is the same as in this cookbook recipe, only turned into a plugin: Restricting access to your site | Kirby CMS

1 Like

That’s an excellent guide and will help as a reference. Thank you!

You wrote in the guide

Note that using users from a database as described in this recipe will not solve potential performance issues with thousands of users.

Can you please tell why this wouldn’t solve potential performance issues? I assume it’s because all users are loaded into memory (at least that’s my experience with virtual pages in Kirby).

Because the users table in the Panel will always be loaded at once. And yes, the same applies to virtual pages, maybe less so because you can create a tree like structure to prevent this.

1 Like

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.

:bulb: User is not getting checked while stored in session!

If there are changes to the 3rd Party user while logged in, they won’t be reflected right away, so short living sessions might be an option.