First, if vendor
directory has not been created, you have to install composer dependencies using:
composer install
You can install this plugin into your CakePHP application using composer.
The recommended way to install composer packages is:
composer require bedita/web-tools
This helper provides some methods to setup Custom Elements with some app variables in order to initialize client side JavaScript components. It aims to avoid the generation of inline JS dictionaries or variables using declarative assignments to HTML nodes. String and numeric values are added as node attributes, while objects and arrays using inline scripts.
Create a js file in the webroot/js
which contains the Custom Element definition:
webroot/js/components/awesome-video.js
class AwesomeVideo extends HTMLElement {
connectedCallback() {
this.video = document.createElement('video');
this.video.src = this.getAttribute('src');
this.appendChild(this.video);
}
}
customElements.define('awesome-video', AwesomeVideo);
Now you can initialize the element in a twig template:
templates/Pages/document.twig
{{ WebComponents.element('awesome-video', { src: attach.uri }, 'components/awesome-video') }}
You can also extends native tags in order to setup simple interactions with the is
method:
webroot/js/components/awesome-table.js
class AwesomeTable extends HTMLElement {
connectedCallback() {
this.addEventListener('click', (event) => {
let th = event.target.closest('[sort-by]');
if (th) {
this.sortBy(th.getAttribute('sort-by'));
}
}):
}
sortBy(field) {
// ...
}
}
customElements.define('awesome-table', AwesomeTable, { extends: 'table' });
templates/Pages/users.twig
<table {{ WebComponents.is('awesome-table', {}, 'components/awesome-table')|raw }}>
<thead>
<th>Email</th>
<th>Name</th>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.attributes.email }}</td>
<td>{{ user.attributes.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
AssetRevisions
with the help of an asset strategy can easily resolve the common issue
of loading built versioned assets as js
and css
.
Through \BEdita\WebTools\View\Helper\HtmlHelper
you can transparently link built assets placed in a custom folder or raw assets living in webroot/js
or webroot/css
.
The best place to define which strategy your app will use is the Application::bootstrap()
use BEdita\WebTools\Utility\AssetRevisions;
use BEdita\WebTools\Utility\Asset\Strategy\EntrypointsStrategy;
public function bootstrap(): void
{
parent::bootstrap();
AssetsRevisions::setStrategy(new EntrypointsStrategy());
// you can also set the path where to find the manifest (default is webroot/build/entrypoints.json)
// AssetsRevisions::setStrategy(
// new EntrypointsStrategy(['manifestPath' => '/custom/path/entrypoints.json']);
// );
}
There are two assets strategies out of the box:
EntrypointsStrategy
based on theentrypoints.json
file generated by Webpack EncoreRevManifestStrategy
based onrev-manifest.json
file generated by gulp-rev
Anyway you are free to define your own strategy implementing AssetStrategyInterface
.
Once a strategy is set you can link assets using \BEdita\WebTools\View\Helper\HtmlHelper
and its methods script()
, css()
and assets()
, for example:
<?= $this->Html->script('app') ?>
The javascript app
asset will be searched first from your asset strategy falling back to CakePHP HtmlHelper
if strategy doesn't resolve the asset.
In this way you can continue to load assets as it was placed in common webroot/js
or webroot/css
and delegate to \BEdita\WebTools\View\Helper\HtmlHelper
the task of resolve the link to them.
ApiIdentifier
is an identifier of Authentication plugin that helps to identify a user through the BEdita API.
In order to use the identifier you need to install and load Authentication plugin in the application bootstrap in Application.php
Install
composer require cakephp/authentication
then load in app
public function bootstrap(): void
{
parent::bootstrap();
$this->addPlugin('Authentication');
}
then add the AuthenticationMiddleware
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// Various other middlewares for error handling, routing etc. added here.
// Create an authentication middleware object
$authentication = new AuthenticationMiddleware($this);
// Add the middleware to the middleware queue.
// Authentication should be added *after* RoutingMiddleware.
// So that subdirectory information and routes are loaded.
$middlewareQueue->add($authentication);
return $middlewareQueue;
}
and take advantage of getAuthenticationService()
hook to set up the identifier.
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
// Load the authenticators, you want session first
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'loginUrl' => '/users/login'
]);
// Load identifiers
$service->loadIdentifier('BEdita/WebTools.Api');
return $service;
}
To use them ensure to install Authentication plugin
composer require cakephp/authentication
and load plugin $this->addPlugin('Authentication')
in Application::bootstrap()
.
Then setup your application to use Identity
, for example
// Edit Application.php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService([
'identityClass' => \BEdita\WebTools\Identity::class
]);
// Load the authenticators, you want session first
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form');
// Load identifiers
$service->loadIdentifier('BEdita/WebTools.Api');
return $service;
}
Identity
exposes a handy hasRole()
method:
// in a Controller
$identity = $this->Authentication->getIdentity();
if ($identity->hasRole('admin')) {
$this->Flash->success('Hi admin!');
}
IdentityHelper
allows to delegate configured methods to Identity
, for example in a TwigView
template
{% if Identity.hasRole('basic') %}
<button type="button">Upgrade to premium</button>
{% endif %}
Using the RequestPolicy
class it is possible to setup the access to controller and actions by identity's roles
or by custom policy rules.
First of all install Authorization plugin
composer require cakephp/authorization
and load plugin $this->addPlugin('Authorization')
in Application::bootstrap()
.
Then proceed with setup the policy in Application
class.
Add the AuthorizationMiddleware
and the RequestAuthorizationMiddleware
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue {
// other middleware...
// $middlewareQueue->add(new AuthenticationMiddleware($this));
// Add authorization (after authentication if you are using that plugin too).
$middlewareQueue->add(new AuthorizationMiddleware($this));
$middlewareQueue->add(new RequestAuthorizationMiddleware());
}
and configure the policy
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$mapResolver = new MapResolver();
$mapResolver->map(
ServerRequest::class,
new RequestPolicy([
// setup your request policy rules
'rules' => [
'Dashboard' => [
'index' => ['premium', 'basic'], // allow access to DashboardController::index() for these roles
'special' => 'premium', // allow access to Dashboard::special() only for 'premium' role
'*' => false, // fallback for other DashboardController actions. Forbidden to all
],
],
]);
);
return new AuthorizationService($mapResolver);
}
Quick steps to use the OAuth2
tools provided.
- Create a route to a path like
/ext/login/{provider}
to interact with the selected OAuth2 provider inconfig/routes.php
. Each{provider}
must match a provider configuration key, for instancegoogle
in the configuration example below, see OAuth2 providers structure. An example here:
$builder->connect(
'/ext/login/{provider}',
['controller' => 'ExternalLogin', 'action' => 'login'],
['_name' => 'login:oauth2']
);
- Define a controller for the above routing rule. A minimal version of the login action could include just a simple redirect like this example:
public function login(): ?Response
{
$result = $this->Authentication->getResult();
if (!empty($result) && $result->isValid()) {
$target = $this->Authentication->getLoginRedirect() ?? ['_name' => 'home'];
return $this->redirect($target);
}
// Handle authentication failure below with flash messages, logs, redirects...
// ....
}
-
Setup
OAuth2Authenticator
andOAuth2Identifier
classes in your mainApplication
class and create the correspondingOAuth2Providers
configuration. See the paragraphs below for more details. -
Add the
OAuth2Middleware
in your middleware stack just after theAuthenticationMiddleware
inApplication::middleware()
. like this:
->add(new AuthenticationMiddleware($this))
->add(new OAuth2Middleware())
To use OAuth2Authenticator
and OAuth2Identifier
classes you must pass the supported OAuth2 providers configuration when loading this classes in the authentication service.
Here a brief example of how to do this in Application::getAuthenticationService()
:
$service = new AuthenticationService();
$path = $request->getUri()->getPath();
if (strpos($path, '/ext/login') === 0) {
$providers = (array)Configure::read('OAuth2Providers');
$service->loadIdentifier('BEdita/WebTools.OAuth2', compact('providers') + [
'autoSignup' => true,
'signupRoles' => ['customer'],
]);
$service->loadAuthenticator('BEdita/WebTools.OAuth2', compact('providers') + [
'redirect' => ['_name' => 'login:oauth2'],
]);
}
We are setting up the OAUth2 authenticator and identifier only when the request path matches our oauth2 login route as defined above.
It is recommended to use a configuration key like OAuth2Providers
to store the provider information, anyway you must pass providers settings array using the providers
key.
Other possibile configuration are:
- (
OAuth2Authenticator
)'redirect'
- default['_name' => 'login']
, redirect url route specified as named array - (
OAuth2Identifier
)autoSignup
- defaultfalse
, set totrue
if you want an automatic signup to be performed if login fails - (
OAuth2Identifier
)'signupRoles'
- default[]
, user roles to use during the signup process, used only ifautoSignup
istrue
The providers configuration structure is in the following example.
Here a single google
provider is defined.
Mandatory configuration keys are class
, setup
, options
and map
explained below.
Each provider key must match the auth_provider
name defined and configured in BEdita API.
'google' => [
// OAuth2 class name, must be a supported provider of `league/oauth2-client`
// see https://oauth2-client.thephpleague.com/providers/league/ official or third-party
'class' => '\League\OAuth2\Client\Provider\Google',
// Provider class setup parameters, normally this includes `clientId` and `clientSecret` keys
// Other parameters like 'redirectUri' will be added dynamically
'setup' => [
'clientId' => '####',
'clientSecret' => '####',
],
// Provider authorization options, specify the user information scope that you want to read
// `'scope'` array will vary between providers, please read the oauth2-client documentation.
'options' => [
'scope' => ['https://www.googleapis.com/auth/userinfo.email'],
],
// Map BEdita user fields with auth provider data path, using dot notation like 'user.id'
// In this array keys are BEdita fields, and values are paths to extract the desired item from the provider response
// only `'provider_username'` is mandatory, to uniquely identify the user in the provider context
// other fields could be used during signup
'map' => [
'provider_username' => 'sub',
'username' => 'email',
'email' => 'email',
'name' => 'given_name',
'surname' => 'family_name',
],
],
For a brief OAuth2 providers reference have a look at the OAuth2 providers configuration wiki page