Skip to content

Instantly share code, notes, and snippets.

@abbadon1334
Created December 3, 2019 14:18
Show Gist options
  • Save abbadon1334/050260d1b117a86a8dcdfd0cbf4e3bcd to your computer and use it in GitHub Desktop.
Save abbadon1334/050260d1b117a86a8dcdfd0cbf4e3bcd to your computer and use it in GitHub Desktop.
atk4/auth0 WIP
<?php
namespace atk4\Auth0;
use atk4\Auth0\Model\Auth0User;
use atk4\core\AppScopeTrait;
use atk4\core\ContainerTrait;
use atk4\core\DIContainerTrait;
use atk4\core\HookTrait;
use atk4\core\InitializerTrait;
use atk4\core\TrackableTrait;
use atk4\data\Model;
use atk4\data\Persistence;
use atk4\ui\App;
use atk4\ui\Callback;
use atk4\ui\Exception;
use Auth0\SDK\Auth0 as SDKAuth0;
use Auth0\SDK\Exception\ApiException;
use Auth0\SDK\Exception\CoreException;
class Auth0
{
use InitializerTrait {
init as _init;
}
use AppScopeTrait;
use DIContainerTrait;
use HookTrait;
use ContainerTrait;
use TrackableTrait;
/**
* Official SDK Auth0
*
* @var SDKAuth0
*/
private $auth0;
/**
* Auth0 config
*
* @var array
*/
private $auth0_config;
/**
* Model used to store Auth0 user data
*
* @var Auth0User
*/
private $auth0_user_model;
/** @var Auth0FieldsMapper */
private $auth0_fields_mapper;
/**
* Application user model
*
* @var Model
*/
private $app_user_model;
/**
* Auth0 constructor.
* @param array $defaults
*
* @throws Exception
*/
public function __construct(array $defaults)
{
$this->setDefaults($defaults);
$this->validateConfiguration();
$this->short_name = 'auth0';
}
/**
* Validate Auth0 configuration
*
* @throws Exception
*/
private function validateConfiguration(): void
{
if (empty($this->auth0_config)) {
$exc = new Exception(['definition of auth0_config is needed']);
$exc->addSolution('you need to define the array of configuration for Auth0 service');
throw $exc;
}
if (!is_a($this->app_user_model, Model::class, true)) {
throw new Exception(['definition of app_user_model is needed and be of type ' . Model::class]);
}
if (!is_a($this->auth0_fields_mapper, Auth0FieldsMapper::class, true)) {
throw new Exception(['definition of auth0_fields_mapper is needed and be of type ' . Auth0FieldsMapper::class]);
}
}
public function init()
{
$this->_init();
$this->addAppMethods();
$this->addCallbackLogin();
$this->auth0Connect();
}
/**
* Add Dynamic methods to App
*
* @throws \atk4\core\Exception
*/
private function addAppMethods(): void
{
$this->app->addMethod('getAuth', function (App $app) {
return $this;
});
}
/**
* Add CallbackLater for login to Auth0
*
* @throws Exception
* @throws \atk4\core\Exception
*/
private function addCallbackLogin(): void
{
/** @var Callback $callback */
$callback = $this->app->add([
'CallbackLater',
'short_name' => 'auth0_callback'
]);
$callback->set(function () {
error_log(__METHOD__);
if (!$this->isLogged()) {
throw new Exception(['there was an error logging in']);
}
$this->app->hook('onAfterUserLogin', [$this->app_user_model]);
});
if (strpos($this->auth0_config['redirect_uri'], $callback->getURL()) === false) {
throw new Exception([
'you need to add "' . $callback->getURL() . '" at the end of your redirect_uri configuration'
]);
}
}
/**
* Return if the user is logged
*
* @return bool
*/
public function isLogged(): bool
{
return $this->getUser()->loaded();
}
/**
* Return the current logged user
*
* @return Model
*/
public function getUser(): Model
{
return $this->app_user_model;
}
/**
* Check if session has token and load the app_user, if not it will call the Auth0 login.
*
* @throws Exception
* @throws Exception\ExitApplicationException
* @throws ApiException
* @throws CoreException
* @throws \atk4\core\Exception
* @throws \atk4\data\Exception
*/
private function auth0Connect(): void
{
$this->auth0 = new SDKAuth0($this->auth0_config);
$user_data = $this->auth0->getUser();
if (null === $user_data) {
if (($_GET['code'] ?? null) !== null) {
throw new Exception([
'There was an error on Login'
]);
}
$this->login();
}
$this->auth0_user_model = new Auth0User(new Persistence\Static_([$user_data]));
$this->auth0_user_model->tryLoadAny();
$this->mapApplicationUserModel();
}
/**
* Call Auth0 Login
*
* @throws Exception\ExitApplicationException
* @throws \atk4\core\Exception
*/
private function login(): void
{
$this->app->hook('onBeforeUserLogin', []);
// redirect to auth0 login
$this->app->redirect($this->auth0->getLoginUrl());
}
/**
* Populate the date using the Mapped fields Auth0->UserModel
*
* @throws \atk4\core\Exception
* @throws \atk4\data\Exception
*/
private function mapApplicationUserModel(): void
{
$this->app_user_model->tryLoadBy(
$this->auth0_fields_mapper->getMappedField('email'),
$this->auth0_user_model->get('email')
);
foreach ($this->auth0_user_model->getFields() as $fieldNameAuth0 => $fieldNameUser) {
$field = $this->auth0_fields_mapper->getMappedField($fieldNameAuth0);
if ($field) {
$this->app_user_model->set($field, $this->auth0_user_model->get($fieldNameAuth0));
}
}
$this->app_user_model->save();
}
/**
* Call Logout, clear local session and call Auth0 logout url to remote logout.
*
* @throws Exception\ExitApplicationException
* @throws \atk4\core\Exception
*/
public function logout()
{
$this->app->hook('onBeforeUserLogout', [$this->app_user_model]);
$this->auth0->logout();
$this->app->hook('onAfterUserLogout', []);
// remote Auth0 logout
$logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', $this->auth0_config['domain'], $this->auth0_config['client_id'], $this->auth0_config['returnTo']);
$this->app->redirect($logout_url);
}
}
<?php
define('AUTH0_DOMAIN','***');
define('AUTH0_CLIENT_ID', '***');
define('AUTH0_CLIENT_SECRET', '***');
<?php
namespace atk4\Auth0;
use atk4\core\Exception;
class Auth0FieldsMapper
{
private $fields = [
'given_name' => null,
'family_name' => null,
'nickname' => null,
'picture' => null,
'locale' => null,
'updated_at' => null,
'email' => null,
'email_verified' => null,
];
/**
* Map an Auth0 field to UserModel Field
*
* @param string $auth0_field
* @param string $atk_field
* @return $this
* @throws Exception
*/
public function setField(string $auth0_field, string $atk_field): self
{
if (!array_key_exists($auth0_field, $this->fields)) {
throw new Exception([$auth0_field . ' is not a normalized Auth0 field']);
}
$this->fields[$auth0_field] = $atk_field;
return $this;
}
/**
* Return a mapped Field by name
*
* @param $auth0_field
* @return string|null
*/
public function getMappedField($auth0_field): ?string
{
return $this->fields[$auth0_field] ?? null;
}
}
<?php
namespace atk4\Auth0\Model;
use atk4\data\Model;
/**
* Auth0 User Model used to store data from Auth0 UserData.
*/
class Auth0User extends Model
{
public $caption = 'User';
public $id_field = 'email';
public function init()
{
parent::init();
$this->addField('given_name');
$this->addField('family_name');
$this->addField('nickname');
$this->addField('picture');
$this->addField('locale');
$this->addField('updated_at');
$this->addField('email_verified');
}
}
{
"name": "abbadon1334/atk-auth0",
"description": "Authentication using Auth0",
"minimum-stability": "dev",
"license": "MIT",
"authors": [
{
"name": "Francesco Danti",
"email": "me@francescodanti.com"
}
],
"require": {
"auth0/auth0-php": "^5.6",
"atk4/ui": "dev-develop@dev"
},
"require-dev": {
"atk4/schema": "dev-develop"
},
"autoload": {
"psr-4": {
"atk4\\Auth0\\" : "src"
}
}
}
<?php
define('AUTH0_DOMAIN','***');
define('AUTH0_CLIENT_ID', '***');
define('AUTH0_CLIENT_SECRET', '***');
<?php
require_once 'auth0_secret.php';
use atk4\Auth0\Auth0;
use atk4\Auth0\Auth0FieldsMapper;
use atk4\data\Model;
use atk4\schema\Migration;
use atk4\ui\App;
use atk4\ui\Layout\Centered;
require_once "../vendor/autoload.php";
class ApplicationUser extends Model
{
public $table = 'user';
public function init()
{
parent::init();
$this->addField('email');
}
}
$db = new atk4\data\Persistence\SQL('sqlite::memory:');
Migration::getMigration(new ApplicationUser($db))->migrate();
$app = new App([
'title' => 'Auth0 test',
'always_run' => false,
'call_exit' => false
]);
$app->initLayout(Centered::class);
$app->addHook('onBeforeUserLogin', function(App $app) {
});
$app->addHook('onAfterUserLogin', function(App $app, ApplicationUser $user) {
$app->redirect('/'); // clear url
});
$app->addHook('onBeforeUserLogout', function(App $app, ApplicationUser $user) {
});
$app->addHook('onAfterUserLogout', function(App $app) {
});
$app->add([
Auth0::class,
[
'auth0_config' => [
'domain' => AUTH0_DOMAIN,
'client_id' => AUTH0_CLIENT_ID,
'client_secret' => AUTH0_CLIENT_SECRET,
'redirect_uri' => 'http://127.0.0.1/index.php?atk_centered_auth0_callback=callback&__atk_callback=1',
'returnTo' => 'http://127.0.0.1/',
'scope' => 'profile email',
'persist_id_token' => true,
//'persist_access_token' => true,
'persist_refresh_token' => true,
'debug' => true
],
'app_user_model' => new ApplicationUser($db),
'auth0_fields_mapper' => (new Auth0FieldsMapper())->setField('email', 'email')
]
]);
/** @var \atk4\ui\Callback $callback */
$callback = $app->add(['Callback']);
$callback->set(function(App $app) {
$app->getAuth()->logout();
}, [$app]);
$app->add(['Button', 'logout ' . $app->getAuth()->getUser()['email']])->link($callback->getURL());
$app->run();
<?php
require_once 'auth0_secret.php';
use atk4\Auth0\Auth0;
use atk4\Auth0\Auth0FieldsMapper;
use atk4\data\Model;
use atk4\schema\Migration;
use atk4\ui\App;
use atk4\ui\Layout\Centered;
require_once "../vendor/autoload.php";
class ApplicationUser extends Model
{
public $table = 'user';
public function init()
{
parent::init();
$this->addField('email');
}
}
$db = new atk4\data\Persistence\SQL('sqlite::memory:');
Migration::getMigration(new ApplicationUser($db))->migrate();
$app = new App([
'title' => 'Auth0 test',
'always_run' => false,
'call_exit' => false
]);
$app->initLayout(Centered::class);
$app->addHook('onBeforeUserLogin', function(App $app) {
});
$app->addHook('onAfterUserLogin', function(App $app, ApplicationUser $user) {
$app->redirect('/'); // clear url
});
$app->addHook('onBeforeUserLogout', function(App $app, ApplicationUser $user) {
});
$app->addHook('onAfterUserLogout', function(App $app) {
});
$app->add([
Auth0::class,
[
'auth0_config' => [
'domain' => AUTH0_DOMAIN,
'client_id' => AUTH0_CLIENT_ID,
'client_secret' => AUTH0_CLIENT_SECRET,
'redirect_uri' => 'http://127.0.0.1/index.php?atk_centered_auth0_callback=callback&__atk_callback=1',
'returnTo' => 'http://127.0.0.1/',
'scope' => 'profile email',
'persist_id_token' => true,
//'persist_access_token' => true,
'persist_refresh_token' => true,
'debug' => true
],
'app_user_model' => new ApplicationUser($db),
'auth0_fields_mapper' => (new Auth0FieldsMapper())->setField('email', 'email')
]
]);
/** @var \atk4\ui\Callback $callback */
$callback = $app->add(['Callback']);
$callback->set(function(App $app) {
$app->getAuth()->logout();
}, [$app]);
$app->add(['Button', 'logout ' . $app->getAuth()->getUser()['email']])->link($callback->getURL());
$app->run();
<?php
namespace atk4\Auth0\Model;
use atk4\data\Model;
/**
* Auth0 User Model used to store data from Auth0 UserData.
*/
class Auth0User extends Model
{
public $caption = 'User';
public $id_field = 'email';
public function init()
{
parent::init();
$this->addField('given_name');
$this->addField('family_name');
$this->addField('nickname');
$this->addField('picture');
$this->addField('locale');
$this->addField('updated_at');
$this->addField('email_verified');
}
}
<?php
namespace atk4\Auth0;
use atk4\Auth0\Model\Auth0User;
use atk4\core\AppScopeTrait;
use atk4\core\ContainerTrait;
use atk4\core\DIContainerTrait;
use atk4\core\HookTrait;
use atk4\core\InitializerTrait;
use atk4\core\TrackableTrait;
use atk4\data\Model;
use atk4\data\Persistence;
use atk4\ui\App;
use atk4\ui\Callback;
use atk4\ui\Exception;
use Auth0\SDK\Auth0 as SDKAuth0;
use Auth0\SDK\Exception\ApiException;
use Auth0\SDK\Exception\CoreException;
class Auth0
{
use InitializerTrait {
init as _init;
}
use AppScopeTrait;
use DIContainerTrait;
use HookTrait;
use ContainerTrait;
use TrackableTrait;
/**
* Official SDK Auth0
*
* @var SDKAuth0
*/
private $auth0;
/**
* Auth0 config
*
* @var array
*/
private $auth0_config;
/**
* Model used to store Auth0 user data
*
* @var Auth0User
*/
private $auth0_user_model;
/** @var Auth0FieldsMapper */
private $auth0_fields_mapper;
/**
* Application user model
*
* @var Model
*/
private $app_user_model;
/**
* Auth0 constructor.
* @param array $defaults
*
* @throws Exception
*/
public function __construct(array $defaults)
{
$this->setDefaults($defaults);
$this->validateConfiguration();
$this->short_name = 'auth0';
}
/**
* Validate Auth0 configuration
*
* @throws Exception
*/
private function validateConfiguration(): void
{
if (empty($this->auth0_config)) {
$exc = new Exception(['definition of auth0_config is needed']);
$exc->addSolution('you need to define the array of configuration for Auth0 service');
throw $exc;
}
if (!is_a($this->app_user_model, Model::class, true)) {
throw new Exception(['definition of app_user_model is needed and be of type ' . Model::class]);
}
if (!is_a($this->auth0_fields_mapper, Auth0FieldsMapper::class, true)) {
throw new Exception(['definition of auth0_fields_mapper is needed and be of type ' . Auth0FieldsMapper::class]);
}
}
public function init()
{
$this->_init();
$this->addAppMethods();
$this->addCallbackLogin();
$this->auth0Connect();
}
/**
* Add Dynamic methods to App
*
* @throws \atk4\core\Exception
*/
private function addAppMethods(): void
{
$this->app->addMethod('getAuth', function (App $app) {
return $this;
});
}
/**
* Add CallbackLater for login to Auth0
*
* @throws Exception
* @throws \atk4\core\Exception
*/
private function addCallbackLogin(): void
{
/** @var Callback $callback */
$callback = $this->app->add([
'CallbackLater',
'short_name' => 'auth0_callback'
]);
$callback->set(function () {
error_log(__METHOD__);
if (!$this->isLogged()) {
throw new Exception(['there was an error logging in']);
}
$this->app->hook('onAfterUserLogin', [$this->app_user_model]);
});
if (strpos($this->auth0_config['redirect_uri'], $callback->getURL()) === false) {
throw new Exception([
'you need to add "' . $callback->getURL() . '" at the end of your redirect_uri configuration'
]);
}
}
/**
* Return if the user is logged
*
* @return bool
*/
public function isLogged(): bool
{
return $this->getUser()->loaded();
}
/**
* Return the current logged user
*
* @return Model
*/
public function getUser(): Model
{
return $this->app_user_model;
}
/**
* Check if session has token and load the app_user, if not it will call the Auth0 login.
*
* @throws Exception
* @throws Exception\ExitApplicationException
* @throws ApiException
* @throws CoreException
* @throws \atk4\core\Exception
* @throws \atk4\data\Exception
*/
private function auth0Connect(): void
{
$this->auth0 = new SDKAuth0($this->auth0_config);
$user_data = $this->auth0->getUser();
if (null === $user_data) {
if (($_GET['code'] ?? null) !== null) {
throw new Exception([
'There was an error on Login'
]);
}
$this->login();
}
$this->auth0_user_model = new Auth0User(new Persistence\Static_([$user_data]));
$this->auth0_user_model->tryLoadAny();
$this->mapApplicationUserModel();
}
/**
* Call Auth0 Login
*
* @throws Exception\ExitApplicationException
* @throws \atk4\core\Exception
*/
private function login(): void
{
$this->app->hook('onBeforeUserLogin', []);
// redirect to auth0 login
$this->app->redirect($this->auth0->getLoginUrl());
}
/**
* Populate the date using the Mapped fields Auth0->UserModel
*
* @throws \atk4\core\Exception
* @throws \atk4\data\Exception
*/
private function mapApplicationUserModel(): void
{
$this->app_user_model->tryLoadBy(
$this->auth0_fields_mapper->getMappedField('email'),
$this->auth0_user_model->get('email')
);
foreach ($this->auth0_user_model->getFields() as $fieldNameAuth0 => $fieldNameUser) {
$field = $this->auth0_fields_mapper->getMappedField($fieldNameAuth0);
if ($field) {
$this->app_user_model->set($field, $this->auth0_user_model->get($fieldNameAuth0));
}
}
$this->app_user_model->save();
}
/**
* Call Logout, clear local session and call Auth0 logout url to remote logout.
*
* @throws Exception\ExitApplicationException
* @throws \atk4\core\Exception
*/
public function logout()
{
$this->app->hook('onBeforeUserLogout', [$this->app_user_model]);
$this->auth0->logout();
$this->app->hook('onAfterUserLogout', []);
// remote Auth0 logout
$logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', $this->auth0_config['domain'], $this->auth0_config['client_id'], $this->auth0_config['returnTo']);
$this->app->redirect($logout_url);
}
}
<?php
namespace atk4\Auth0;
use atk4\core\Exception;
class Auth0FieldsMapper
{
private $fields = [
'given_name' => null,
'family_name' => null,
'nickname' => null,
'picture' => null,
'locale' => null,
'updated_at' => null,
'email' => null,
'email_verified' => null,
];
/**
* Map an Auth0 field to UserModel Field
*
* @param string $auth0_field
* @param string $atk_field
* @return $this
* @throws Exception
*/
public function setField(string $auth0_field, string $atk_field): self
{
if (!array_key_exists($auth0_field, $this->fields)) {
throw new Exception([$auth0_field . ' is not a normalized Auth0 field']);
}
$this->fields[$auth0_field] = $atk_field;
return $this;
}
/**
* Return a mapped Field by name
*
* @param $auth0_field
* @return string|null
*/
public function getMappedField($auth0_field): ?string
{
return $this->fields[$auth0_field] ?? null;
}
}
<?php
namespace atk4\Auth0\Model;
use atk4\data\Model;
/**
* Auth0 User Model used to store data from Auth0 UserData.
*/
class Auth0User extends Model
{
public $caption = 'User';
public $id_field = 'email';
public function init()
{
parent::init();
$this->addField('given_name');
$this->addField('family_name');
$this->addField('nickname');
$this->addField('picture');
$this->addField('locale');
$this->addField('updated_at');
$this->addField('email_verified');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment