Composer
This app has been tested with Drupal 10.1.5. This example should work with other compatible versions of Drupal 10. Drupal 10 requires PHP 8.1.0+ and MariaDB 10.3.7+, MySQL 5.7.8+, or Percona 5.7.8+.
By default, Drupal has a built-in login and authentication system. As such, the application will resemble the following before FusionAuth is introduced.
Request flow during login before FusionAuth
The login flow will look like this after FusionAuth is introduced.
Request flow during login after FusionAuth
In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.
In this section, you’ll get FusionAuth up and running on your machine and create a Drupal application.
First off, grab the code from the repository and change into that directory.
git clone https://github.com/FusionAuth/fusionauth-quickstart-php-drupal-web.git
cd fusionauth-quickstart-php-drupal-web
The complete-application
directory in the repository already contains a complete Drupal application but for demonstration purposes, this tutorial shows how to create a new application from scratch and then switch the volumes in the docker-compose.yml
file to point to the new application.
Start by creating a new Drupal 10 project and changing to the project directory with the following command.
composer create-project drupal/recommended-project:10.1.5 "your-application" && cd your-application
Next, make two changes in the docker-compose.yml
file to allow you to use your new Drupal application.
If you want to use the existing application at any point, revert the following two changes and then rebuild the Docker containers and volumes.
On the web
service, change volumes
from ./complete-application:/opt/drupal:cached
to ./your-application:/opt/drupal:cached
.
Under the db2
service, change the key MYSQL_DATABASE
from drupaldb
to changebank
. This updates the database name so that you are not using the same database as the existing complete application.
Save your changes and then run the following command to get the application up and running.
docker compose up -d
Once the application is up and running, navigate to http://localhost/ and you should be redirected to the Drupal installation wizard at http://localhost/core/install.php.
Run through the wizard and fill in all required fields. Make sure to enter the following.
On Step 1 , choose your language and click Save and continue.
On Step 2 , choose Standard
profile and click Save and continue.
On Step 4 , enter the following database connection details.
MySQL, MariaDB, Percona Server, or equivalent
option.changebank
.drupal
.verybadpassword
.db2
.3307
.Click Save and continue.
On Step 6 , enter the following values.
Change Bank
.admin@example.com
.admin
.verybadpassword
.verybadpassword
.admin@example.com
.Click Save and continue.
Once you’ve completed the installation wizard, you should be taken to the welcome page.
Now you can install OpenID Connect to handle authenticating with FusionAuth and the Admin Toolbar module to add a bit of quality of life when navigating the Drupal administration menu.
In a terminal, run the following command from the your-application
directory.
composer require 'drupal/openid_connect:3.x-dev@dev' 'drupal/admin_toolbar:^3.4'
You can enable the modules by visiting the extensions page of your Drupal application at http://localhost/admin/modules. Search for “OpenID Connect” and “Admin Toolbar” using the Filter field. Select the module checkboxes and click the Install button.
Alternatively, you can use Drush to enable the modules. Drush is a command line shell and Unix scripting interface for Drupal, available as a composer package. Install it by running the following command.
composer require drush/drush
You might need to restart your containers for the changes to take effect. You can do so by running the following command.
docker compose restart
Once the containers are up and running, connect to the Drupal container terminal to run Drush commands.
docker compose exec web bash
Run the following command to enable the modules.
vendor/bin/drush pm-enable openid_connect admin_toolbar
Confirm with yes when prompted.
Do you want to continue? (yes/no) [yes]:
yes
You can use Drush to run many commands and perform common tasks in Drupal. Other popular commands include:
vendor/bin/drush cr
clears the cache.vendor/bin/drush pm-enable <module name>
enables a module.vendor/bin/drush updb
updates the database.vendor/bin/drush uli
generates a one-time login link for a user.For the full list of Drush commands, visit https://www.drush.org/12.2.0/commands/all/.
You'll find a Docker Compose file (docker-compose.yml
) and an environment variables configuration file (.env
) in the root directory of the repo.
Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.
docker compose up -d
Here you are using a bootstrapping feature of FusionAuth called Kickstart. When FusionAuth comes up for the first time, it will look at the kickstart/kickstart.json
file and configure FusionAuth to your specified state.
If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing docker compose down -v
, then re-run docker compose up -d
.
FusionAuth will be initially configured with these settings:
e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
.super-secret-secret-that-should-be-regenerated-for-production
.richard@example.com
and the password is password
.admin@example.com
and the password is password
.http://localhost:9011/
.You can log in to the FusionAuth admin UI and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.
If you want to see where the FusionAuth values came from, they can be found in the FusionAuth app. The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the View
icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the OAuth configuration
section.
The .env
file contains passwords. In a real application, always add this file to your .gitignore
file and never commit secrets to version control.
To set up your Drupal application to use FusionAuth for authentication, head to http://localhost/admin/config/people/openid-connect on your Drupal site.
Add a new client by clicking on + Generic OAuth 2.0 and fill in the details as follows:
FusionAuth
e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
super-secret-secret-that-should-be-regenerated-for-production
http://host.docker.internal:9011/oauth2/authorize
http://host.docker.internal:9011/oauth2/token
http://host.docker.internal:9011/oauth2/userinfo
You may need to update your hosts file to include an entry for 127.0.0.1 host.docker.internal
if it’s not already present. On macOS and Linux you can achieve this by running the following command:
echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts
Scroll down and click Create OpenID Connect client.
While on the page that lists your newly created client, click the Settings tab. Under the section OpenID buttons display in user login form , change the value from Hidden
to Below
to display the “Login with FusionAuth” button on the login page.
Scroll down and click Save configuration to save the settings.
Before you test the authentication flow, you need to make a small change to allow visitors to create accounts on the Drupal application. Navigate to http://localhost/admin/config/people/accounts and change Who can register accounts? to Visitors
and uncheck Require email verification when a visitor creates an account . Save the configuration.
You need to flush the cache for the changes to take effect. You can flush the cache in Drupal in two ways:
vendor/bin/drush cr
command in the Drupal Docker container terminal.Now you can test the authentication flow.
In the browser, navigate to http://localhost/user/logout to log out of the Drupal application. Navigate to http://localhost/user/login and click on the Login with FusionAuth button.
You should be taken to the FusionAuth login page. Log in with the username richard@example.com
and password password
.
You should be redirected back to the Drupal application once logged in.
In this section, you’ll turn your application into a trivial banking application with some styling.
You’re going to create a module called “Changebank” that will handle the logic for making change from dollars, and add a couple of blocks to the application.
In your terminal, run the following command in the Drupal Docker container.
vendor/bin/drush generate module
You will be presented with the module creation wizard. Fill it out as follows:
Welcome to module generator!
Module name:
Changebank
Module machine name [changebank]:
changebank
Module description:
Provides a way to make change from dollars.
Package [Custom]:
Custom
Dependencies (comma separated):
Would you like to create module file [No]:
Yes
Would you like to create install file [No]:
No
Would you like to create README.md file [No]:
No
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/changebank.info.yml
- /opt/drupal/web/modules/changebank/changebank.module
Now you will expand on the module by generating two blocks, a form, a controller, and a routing file.
To generate the first block, run the following command and complete the wizard.
vendor/bin/drush generate block
You will be presented with the block creation wizard. Fill it out as follows:
Welcome to block generator!
Module machine name:
changebank
Block admin label:
User Email Block
Plugin ID [changebank_user_email_block]:
useremailblock
Plugin class [UseremailblockBlock]:
UserEmailBlock
Block category [Custom]:
Custom
Make the block configurable? [No]:
No
Would you like to inject dependencies? [No]:
No
Create access callback? [No]:
No
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/src/Plugin/Block/UserEmailBlock.php
Generate the second block with the same block generation command as before.
vendor/bin/drush generate block
Enter the following information:
Welcome to block generator!
Module machine name:
changebank
Block admin label:
User Login Logout Block
Plugin ID [changebank_user_login_logout_block]:
userloginlogoutlock
Plugin class [UserloginlogoutlockBlock]:
UserLoginLogoutBlock
Block category [Custom]:
Custom
Make the block configurable? [No]:
No
Would you like to inject dependencies? [No]:
No
Create access callback? [No]:
No
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/src/Plugin/Block/UserLoginLogoutBlock.php
To generate the form, run the following command.
vendor/bin/drush generate form
You will be presented with the form wizard. Fill it out as follows:
Welcome to form generator!
Module machine name:
changebank
Module name [Changebank]:
Changebank
Class [ExampleForm]:
ChangebankForm
Would you like to create a route for this form? [Yes]:
No
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/src/Form/ChangebankForm.php
Next, generate a controller with the following command.
vendor/bin/drush generate controller
You will be presented with the controller wizard. Fill it out as follows:
Welcome to controller generator!
Module machine name:
changebank
Module name [Changebank]:
Changebank
Class [ChangebankController]:
ChangebankController
Would you like to inject dependencies? [No]:
No
Would you like to create a route for this controller? [Yes]:
No
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/src/Controller/ChangebankController.php
Finally, generate a routing file with the following command.
vendor/bin/drush generate routing
You will be presented with the routing wizard. Fill it out as follows:
Module machine name:
changebank
Module name [Changebank]:
Changebank
The following directories and files have been created or updated:
- /opt/drupal/web/modules/changebank/changebank.routing.yml
Now that you have all the files you need in your custom module, you can customize them.
You will be editing the following files:
ChangebankForm.php
ChangebankController.php
changebank.routing.yml
UserEmailBlock.php
UserLoginLogoutBlock.php
changebank.module
In your text editor, select the ChangebankForm.php
file located at your-application/web/modules/changebank/src/Form/ChangebankForm.php
. Clear the existing boilerplate contents and add the following code.
<?php
declare(strict_types = 1);
namespace Drupal\changebank\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a form for creating change from an amount in US dollars.
*/
class ChangebankForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'changebank_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$result = $form_state->get('result');
$form['result_wrapper'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['changebank-result-wrapper'],
],
];
if ($form_state->get('result')) {
$form['result_wrapper']['result'] = [
'#markup' => $result,
];
}
$form['amount'] = [
'#type' => 'number',
'#title' => $this->t('Amount in USD: $'),
'#required' => TRUE,
'#min' => 0,
'#step' => 0.01,
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Make Change'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$amount = $form_state->getValue('amount');
$amount_in_cents = round($amount * 100);
$quarters = floor($amount_in_cents / 25);
$amount_in_cents %= 25;
$dimes = floor($amount_in_cents / 10);
$amount_in_cents %= 10;
$nickels = floor($amount_in_cents / 5);
$amount_in_cents %= 5;
$pennies = $amount_in_cents;
$coins = [];
if ($quarters > 0) {
$coins[] = "@quarters quarters";
}
if ($dimes > 0) {
$coins[] = "@dimes dimes";
}
if ($nickels > 0) {
$coins[] = "@nickels nickels";
}
if ($pennies > 0) {
$coins[] = "@pennies pennies";
}
$result = $this->t('We can make change for $@dollars with ' . implode(', ', $coins) . '!', [
'@dollars' => $amount,
'@quarters' => $quarters,
'@dimes' => $dimes,
'@nickels' => $nickels,
'@pennies' => $pennies,
]);
$form_state->setRebuild(TRUE);
$form_state->set('result', $result);
\Drupal::state()->set('changebank_form_value', $result);
}
}
In your text editor, select the ChangebankController.php
file located at your-application/web/modules/changebank/src/Controller/ChangebankController.php
and replace the content with the following code.
<?php
declare(strict_types = 1);
namespace Drupal\changebank\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Returns responses for changebank routes.
*/
class ChangebankController extends ControllerBase {
/**
* Builds the response.
*/
public function build() {
$build = [];
$form = $this->formBuilder()->getForm('\Drupal\changebank\Form\ChangebankForm');
$build['form'] = $form;
return $build;
}
/**
* Displays the form submission value on the account page.
*/
public function accountBalance() {
$value = \Drupal::state()->get('changebank_form_value');
$arguments = ($value) ? $value->getArguments() : [];
$dollars = $arguments['@dollars'] ?? 0;
return [
'#markup' => '<span class="account-balance">$' . $dollars . '</span>',
'#cache' => [
'contexts' => [
'session',
],
'max-age' => 0,
],
];
}
}
Next you’ll set up two routes, one for the form and one for the account page.
Select the changebank.routing.yml
file located at your-application/web/modules/changebank/changebank.routing.yml
and replace the content with the following configuration.
changebank.makechange:
path: '/makechange'
defaults:
_title: 'We Make Change'
_controller: '\Drupal\changebank\Controller\ChangebankController::build'
_form: 'Drupal\changebank\Form\ChangebankForm'
requirements:
_user_is_logged_in: 'TRUE'
changebank.account:
path: '/account'
defaults:
_title: 'Your Balance'
_controller: '\Drupal\changebank\Controller\ChangebankController::accountBalance'
requirements:
_user_is_logged_in: 'TRUE'
You’ll use the User Email Block to display the current user’s email address in a block. Select the UserEmailBlock.php
file located at your-application/web/modules/changebank/src/Plugin/Block/UserEmailBlock.php
and replace the content with the following code.
<?php
declare(strict_types = 1);
namespace Drupal\changebank\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block that displays the current user's email address.
*
* @Block(
* id = "user_email_block",
* admin_label = @Translation("User Email Block")
* )
*/
class UserEmailBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new UserEmailBlock object.
*
* @param array $configuration
* The block configuration.
* @param string $plugin_id
* The block plugin ID.
* @param mixed $plugin_definition
* The block plugin definition.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function build() {
$email = $this->currentUser->getEmail();
return [
'#markup' => $this->t('@email', ['@email' => $email]),
];
}
}
You’ll use the User Login And Logout Block to display a login button for anonymous users and a logout button for authenticated users.
Select the UserLoginLogoutBlock.php
file located at your-application/web/modules/changebank/src/Plugin/Block/UserLoginLogoutBlock.php
and replace the content with the following code.
<?php
declare(strict_types = 1);
namespace Drupal\changebank\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block that displays linked text for anonymous users.
*
* @Block(
* id = "user_login_logout_block",
* admin_label = @Translation("User Login Logout Block")
* )
*/
class UserLoginLogoutBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new UserLoginLogoutBlock object.
*
* @param array $configuration
* The block configuration.
* @param string $plugin_id
* The block plugin ID.
* @param mixed $plugin_definition
* The block plugin definition.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function build() {
$roles = $this->currentUser->getRoles();
if (in_array('anonymous', $roles)) {
return [
'#markup' => $this->t('<button class="btn btn-primary"><a href="/user/login">Log in</a></button>'),
];
}
else {
return [
'#markup' => $this->t('<button class="btn btn-primary"><a href="/user/logout">Logout</a></button>'),
];
}
}
}
changebank.module
FileTo make the blocks available to the application, you need to make some changes to the changebank.module
file located at your-application/web/modules/changebank/changebank.module
. Add the following.
<?php
declare(strict_types = 1);
/**
* @file
* Primary module hooks for changebank module.
*/
/**
* Implements hook_block_info().
*/
function changebank_block_info() {
$blocks = [];
$blocks['user_email_block'] = [
'info' => t('User Email Block'),
'cache' => DRUPAL_CACHE_PER_ROLE,
];
$blocks['user_login_logout_block'] = [
'info' => t('User Login Logout Block'),
'cache' => DRUPAL_CACHE_PER_ROLE,
];
return $blocks;
}
To enable the Changebank module, navigate to http://localhost/admin/modules while logged in as admin and select the checkbox next to the Changebank module item. Click the Install button.
You can also enable the module by running the following command in the Drupal Docker container.
vendor/bin/drush pm-enable changebank
In this section, you’re going to add a theme to the application, along with a couple of templates and some basic styling.
A theme called “Barrio Bootstrap 5” will give you a good starting point for styling the application. Run the following command from a terminal in the your-application
directory to add the theme to the project.
composer require 'drupal/bootstrap_barrio:^5.5'
Next, you’ll create a subtheme of the Barrio Bootstrap 5 theme.
You will notice a folder called subtheme
in the your-application/web/themes/contrib/bootstrap_barrio
directory.
First, create a directory called custom
in your-application
, and a directory called subtheme
in custom
.
mkdir -p web/themes/custom/subtheme
Copy the your-application/web/themes/contrib/bootstrap_barrio/subtheme
directory into your-application/web/themes/custom/subtheme
.
cp -r web/themes/contrib/bootstrap_barrio/subtheme/* web/themes/custom/subtheme
Enter the subtheme folder and edit the bootstrap_barrio_subtheme.info.yml
file. Change the name of the theme from Bootstrap Barrio Subtheme
to Changebank
, and change the description from Basic structure for a Bootstrap Barrio SubTheme
to Subtheme for Changebank
.
To enable the theme, navigate to http://localhost/admin/appearance and click the Install button for both “Bootstrap Barrio 5.5.14” and “Changebank 5.5.14”.
Click the Set as default button for “Changebank 5.5.14”.
While you are here, let’s add the Changebank logo to the application.
Click Settings on Changebank 5.5.14, then scroll down and click Logo image. Uncheck the Use the logo supplied by the theme checkbox, and upload the Changebank logo in the Upload logo image field. Save the configuration.
The Twig Tweak module will make it easier to render content in the templates you create later. Twig Tweak is a small module that provides a Twig extension with some useful functions and filters that can improve the developer experience.
Run the following command in the your-application
directory to download the module.
composer require 'drupal/twig_tweak:^3.2'
Enable the module by navigating to http://localhost/admin/modules, selecting the checkbox next to Twig Tweak, and clicking the Install button.
You can also enable the module with the following command in the Drupal Docker container.
vendor/bin/drush pm-enable twig_tweak
In the subtheme directory of your application, you’ll add a couple of templates to override the default templates provided by the Bootstrap Barrio theme. This will allow you to customize the markup of the application.
The page--front.html.twig
template file you will create uses an image that is currently not in your application. To copy the image to your application, run the following command from your-application
directory.
cp ../complete-application/web/sites/default/files/money-new.jpg web/sites/default/files/money-new.jpg
Navigate to your-application/web/themes/custom/subtheme/templates/
. If the templates
directory is not there, you may need to create it.
In the templates directory, create the following files:
page--front.html.twig
block--system-branding-block.html.twig
region--secondary-menu.html.twig
In the page template for the front page (called page--front.html.twig
), add the following code.
{% extends "@bootstrap_barrio/layout/page.html.twig" %}
{#
/**
* @file
* Bootstrap Barrio's theme implementation to display a single page.
*
* The doctype, html, head and body tags are not in this template. Instead they
* can be found in the html.html.twig template normally located in the
* core/modules/system directory.
*
* Available variables:
*
* General utility variables:
* - base_path: The base URL path of the Drupal installation. Will usually be
* "/" unless you have installed Drupal in a sub-directory.
* - is_front: A flag indicating if the current page is the front page.
* - logged_in: A flag indicating if the user is registered and signed in.
* - is_admin: A flag indicating if the user has permission to access
* administration pages.
*
* Site identity:
* - front_page: The URL of the front page. Use this instead of base_path when
* linking to the front page. This includes the language domain or prefix.
* - logo: The url of the logo image, as defined in theme settings.
* - site_name: The name of the site. This is empty when displaying the site
* name has been disabled in the theme settings.
* - site_slogan: The slogan of the site. This is empty when displaying the site
* slogan has been disabled in theme settings.
* Page content (in order of occurrence in the default page.html.twig):
* - node: Fully loaded node, if there is an automatically-loaded node
* associated with the page and the node ID is the second argument in the
* page's path (e.g. node/12345 and node/12345/revisions, but not
* comment/reply/12345).
*
* Regions:
* - page.top_header: Items for the top header region.
* - page.top_header_form: Items for the top header form region.
* - page.header: Items for the header region.
* - page.header_form: Items for the header form region.
* - page.highlighted: Items for the highlighted region.
* - page.primary_menu: Items for the primary menu region.
* - page.secondary_menu: Items for the secondary menu region.
* - page.featured_top: Items for the featured top region.
* - page.content: The main content of the current page.
* - page.sidebar_first: Items for the first sidebar.
* - page.sidebar_second: Items for the second sidebar.
* - page.featured_bottom_first: Items for the first featured bottom region.
* - page.featured_bottom_second: Items for the second featured bottom region.
* - page.featured_bottom_third: Items for the third featured bottom region.
* - page.footer_first: Items for the first footer column.
* - page.footer_second: Items for the second footer column.
* - page.footer_third: Items for the third footer column.
* - page.footer_fourth: Items for the fourth footer column.
* - page.footer_fifth: Items for the fifth footer column.
* - page.breadcrumb: Items for the breadcrumb region.
*
* Theme variables:
* - navbar_top_attributes: Items for the header region.
* - navbar_attributes: Items for the header region.
* - content_attributes: Items for the header region.
* - sidebar_first_attributes: Items for the highlighted region.
* - sidebar_second_attributes: Items for the primary menu region.
*
* @see template_preprocess_page()
* @see bootstrap_barrio_preprocess_page()
* @see html.html.twig
*/
#}
{# see https://www.drupal.org/project/drupal/issues/953034#comment-14192130 #}
{% set sidebar_first_exists = page.sidebar_first|render|striptags('<img><video><audio><drupal-render-placeholder>')|trim is not empty %}
{% set sidebar_second_exists = page.sidebar_second|render|striptags('<img><video><audio><drupal-render-placeholder>')|trim is not empty %}
{% block head %}
{% if page.secondary_menu or page.top_header or page.top_header_form %}
<nav{{ navbar_top_attributes }}>
{% if container_navbar %}
<div class="container">
{% endif %}
{{ page.secondary_menu }}
{{ page.top_header }}
{% if page.top_header_form %}
<div class="form-inline navbar-form float-right">
{{ page.top_header_form }}
</div>
{% endif %}
{% if container_navbar %}
</div>
{% endif %}
</nav>
{% endif %}
<nav{{ navbar_attributes }}>
{% if container_navbar %}
<div class="container">
{% endif %}
{{ page.header }}
{% if page.primary_menu or page.header_form %}
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#CollapsingNavbar" aria-controls="CollapsingNavbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="CollapsingNavbar">
{{ page.primary_menu }}
{% if page.header_form %}
<div class="form-inline navbar-form float-right">
{{ page.header_form }}
</div>
{% endif %}
</div>
{% endif %}
{% if sidebar_collapse %}
<button class="navbar-toggler navbar-toggler-left collapsed" type="button" data-toggle="collapse" data-target="#CollapsingLeft" aria-controls="CollapsingLeft" aria-expanded="false" aria-label="Toggle navigation"></button>
{% endif %}
{% if container_navbar %}
</div>
{% endif %}
</nav>
{% endblock %}
{% block content %}
<div id="main" class="container-fluid">
{{ page.breadcrumb }}
<div class="row row-offcanvas row-offcanvas-left clearfix">
<main{{ content_attributes }}>
<section class="section">
<a href="#main-content" id="main-content" tabindex="-1"></a>
<div class="row">
<div class="col col-sm-3 welcome">
<div class="sidebar" style="text-align: center;">
<h1 style="color: #096324; font-weight: 700;">Welcome to Changebank</h1>
<p><strong>To get started, <a href="/user/login" style="color: #096324;">log in</a> or<br/> <a href="/user/login" style="color: #096324;">create a new account</a>.</strong></p>
</div>
</div>
<div class="col col-sm-9" style="height: calc(100vh - 148px); overflow: hidden;">
<img src="/sites/default/files/money-new.jpg" alt="Image" width="100%" height="auto">
</div>
</div>
{# {{ page.content }} #}
</section>
</main>
{% if sidebar_first_exists %}
<div{{ sidebar_first_attributes }}>
<aside class="section" role="complementary">
{{ page.sidebar_first }}
</aside>
</div>
{% endif %}
{% if sidebar_second_exists %}
<div{{ sidebar_second_attributes }}>
<aside class="section" role="complementary">
{{ page.sidebar_second }}
</aside>
</div>
{% endif %}
</div>
</div>
{% endblock %}
Now add markup to create a custom layout for the site logo, the user email block, and the user login and logout block.
First, add the following to the region--secondary-menu.html.twig
template.
{#
/**
* @file
* Theme override to display a region.
*
* Available variables:
* - content: The content for this region, typically blocks.
* - attributes: HTML attributes for the region div.
* - region: The name of the region variable as defined in the theme's
* .info.yml file.
*
* @see template_preprocess_region()
*/
#}
{%
set classes = [
'region',
'region-' ~ region|clean_class,
]
%}
{% if content %}
<section{{ attributes.addClass(classes) }}>
<div class="row">
<div class="col col-xs-9">
{{ drupal_block('system_branding_block') }}
</div>
<div class="col col-xs-3 d-flex justify-content-end login-container">
<div class="email-container">
{{ drupal_block('user_email_block') }}
</div>
<div class="button-container">
{{ drupal_block('user_login_logout_block') }}
</div>
</div>
</div>
{# {{ content }} #}
</section>
{% endif %}
If the user email and login and logout blocks do not display or appear broken, make sure that the Twig Tweak module is enabled as it provides the naming convention to embed the blocks in the template.
Now, as the code adds the System Branding block to the secondary menu region template, you need to manually hide the site name.
Add the following to the block--system-branding-block.html.twig
template.
{#
/**
* @file
* Bootstrap Barrio's theme implementation for a branding block.
*
* Each branding element variable (logo, name, slogan) is only available if
* enabled in the block configuration.
*
* Available variables:
* - site_logo: Logo for site as defined in Appearance or theme settings.
* - site_name: Name for site as defined in Site information settings.
* - site_slogan: Slogan for site as defined in Site information settings.
*/
#}
{% set attributes = attributes.addClass('site-branding') %}
{% if site_logo or site_name %}
<a href="{{ path('<front>') }}" title="{{ 'Home'|t }}" rel="home" class="navbar-brand">
{% if site_logo %}
<img src="{{ site_logo }}" alt="{{ 'Home'|t }}" class="img-fluid d-inline-block align-top" />
{% endif %}
{# {{ site_name }} #}
</a>
{% endif %}
{% if site_slogan %}
<div class="d-inline-block align-top site-name-slogan">
{{ site_slogan }}
</div>
{% endif %}
This code hides the site_name
variable by commenting it out, but you can delete it if you prefer.
Next you’ll add some styling to the application to make it look a little nicer. Select the style.css
file located at your-application/web/themes/custom/subtheme/css/style.css
and add the following.
/**
* @file
* Subtheme specific CSS.
*/
#navbar-top {
background-color: #fff!important;
}
#navbar-main {
background-color: #096324!important;
}
.sidebar h2 {
margin: 0 0 0.5em;
padding-bottom: 5px;
text-shadow: 0 1px 0 #fff;
font-size: 2.071em;
}
.sidebar .block {
border-style: solid;
border-width: 1px;
padding: 15px 15px;
margin: 0 0 20px;
background-color: #fff!important;
border-color: #fff!important;
}
.main-content.col {
padding-right: 0px;
}
.welcome {
display: flex;
justify-content: center;
align-items: center;
height: 320px;
}
body {
overflow-x: hidden;
}
.site-footer {
display: none;
}
#main-wrapper {
height: calc(100vh - 148px);
}
.navbar {
display: block;
}
.email-container {
display: inline-block;
vertical-align: middle;
margin-right: 20px;
}
.button-container {
display: inline-block;
vertical-align: middle;
}
.login-container {
align-items: center;
}
button,
.btn,
.btn-primary {
background-color: #096324!important;
}
button a {
color: #fff;
font-weight: 700;
text-decoration: none;
padding-left: 30px;
padding-right: 30px;
}
button a:hover {
color: #fff;
text-decoration: underline;
}
.block-user-email-block {
color: #096324;
font-weight: 400;
}
.navbar-brand img {
padding: 5px 0px 5px 10px;
height: 70px;
}
#block-bootstrap-barrio-subtheme-page-title {
margin-top: 70px;
}
h1, h2, h3, h4, h5, h6 {
color: #096324;
}
.nav-link {
font-weight: 500;
font-size: 20px;
}
.nav-item .active {
text-decoration: underline;
}
.nav {
margin-right: 7px;
}
.account-balance {
font-size: 32px;
font-weight: 700;
}
Previously, you created the routes for the form and account page. Now you’ll add a menu to the application to make it easier to navigate between the two pages.
Go to http://localhost/admin/structure/menu in the administration menu and click Add menu.
Set Title to Changebank Menu
and click Save.
Click Add link and enter the following:
Account
/account
Scroll down and click Save.
Click on Add link again and enter the following:
Make Change
/makechange
Scroll down and click Save.
Now that you have the User Email, User Login and Logout, and the Changebank Menu blocks, you need to enable them.
Navigate to http://localhost/admin/structure/block in the administration menu.
Scroll down to the Primary Menu section and click Place block.
Changebank Menu
in the Filter field.Authenticated user
role.In your custom module, you set access to the account
and makechange
routes to be restricted to authenticated (logged in) users. You want to make sure that the menu is also only visible to authenticated users.
Scroll down to the Secondary Menu section and click Place block.
User Email Block
in the Filter field.Authenticated user
role.Scroll down to the Secondary Menu section and click Place block.
User Login Logout Block
in the Filter field.By default, Drupal will have a Site Branding block, a Main Navigation block, and a User Account block.
You already have custom login and logout links, so you can remove the User account menu
block. Click the dropdown arrow next to the User account menu
block and select Remove.
To disable Breadcrumbs
, click the dropdown arrow next to the Breadcrumbs
block and select Disable.
The theme you used also has default blocks, like Search Form (wide)
in the top header form and Search Form (narrow)
in the sidebar first section. You don’t need search functionality in this application, so remove these blocks by clicking on the dropdown arrow next to each and selecting Remove.
Now let’s set the Main Navigation block to only be visible to anonymous users. Click the Configure link next to the Main Navigation block. Click the Roles tab under Visibility and select the Anonymous user
role. Click Save block.
Move the Site Branding block to the Secondary Menu section by dragging the block and dropping it into the region above the other two blocks. Scroll down and click Save blocks.
Finally, configure the Site Branding block to only display the logo by unchecking the Site name and Site slogan checkboxes. Click Save block.
Navigate to http://localhost/admin/config/people/openid-connect and click the Settings tab.
Since you have the /account
route set up in your custom module, let’s make sure that the user is redirected to that page after logging in and not the standard user page.
Scroll down to the redirects section and enter account
in the Login redirect field.
Save the configuration and clear the cache. Log out from the application.
You can now open the application at http://localhost/user/login.
Log in by clicking the Login with FusionAuth button and using the username richard@example.com
and password password
.
This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.
FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:
Drupal is a powerful CMS, and there are many ways to customize it. Here are a few things you may want to do:
This site can’t be reached localhost refused to connect.
when I click the login buttonEnsure FusionAuth is running in the Docker container. You should be able to log in as the admin user admin@example.com
with the password password
at http://localhost:9011/admin.
/usr/bin/env: 'php\r': No such file or directory
when trying to run Drush commands.Incorrect line endings are known to cause this issue. To fix the issue, convert the line endings of the file from Windows to Unix. You can use the dos2unix
command. Install dos2unix
by running the following command in the Drupal container.
apt-get update && apt-get install -y dos2unix
Once installed, convert the line endings of the file by running the following command in the Drupal container.
dos2unix /opt/drupal/vendor/bin/drush
The Settings file does not exist.
Drupal will try to automatically create a settings.php
configuration file, which is normally in the directory sites/default
(to avoid problems when upgrading, Drupal is not packaged with this file). If auto-creation fails, you will need to create this file yourself, using the file sites/default/default.settings.php
as a template.
Create a settings file by navigating to your-application/web/sites/default/
, copying default.settings.php
, and renaming it to settings.php
.