The Mijingo Blog

Latest news, updates, free tutorials, and more from Mijingo.

Hashing Redirect Params in Craft

by Ryan Irelan

Back in version 2.6.2945 of Craft 2, Pixel & Tonic introduced the ability to require hashing of the redirect parameters that are set in forms (like when you sign in or sign up).

They introduced this enhancement to make Craft more secure. There is a potential Denial of Service (DoS) security issue with in-the-clear form data. In the example of a redirect parameter, this could allow someone to redirect the user to something other than what is specified in the hidden form element.

We need to protect our form data. Let’s learn how to do that, starting with Craft 2.

Protecting Return Parameters in Craft 2

The solution to protecting the redirect parameter in Craft 2 is two-fold:

  • In Craft 2.5.2750, the developers introduced the hash filter, which takes the input and hashes it using a HMAC (Hashed Message Authentication Code). On the back-end, while processing the request, the developer can validate the hash using a validateData method.
  • In Craft 2.6.2945. the developers introduced a configuration setting that forced each request with a redirect parameter to be hashed. This new config setting is called validateUnsafeRequestParams and it takes a boolean value. When set to true then the unsafe parameters (which is just the redirect parameter and perhaps some third party plugin parameters).

Together, these will protect your form redirect parameter from any security issues.

Let’s use the login form as an example:


<form method="post" accept-charset="UTF-8">
    {{ getCsrfInput() }}
    <input type="hidden" name="action" value="users/login">
    <input type="hidden" name="redirect" value="{{ '/profile' | hash}}">
    <h3><label for="loginName">Username or email</label></h3>
    <input id="loginName" type="text" name="loginName"
        value="{{ craft.session.rememberedUsername }}">

    <h3><label for="password">Password</label></h3>
    <input id="password" type="password" name="password">

    <label>
        <input type="checkbox" name="rememberMe" value="1">
        Remember me
    </label>

    <input type="submit" value="Login">
</form>

We have our standard hidden field with the redirect name and the value is populated with a Twig output tag, a string for our redirect location. We use the hash filter on the string to hash it.

If we load this in the browser we should see that the string is hashed:


<input type="hidden" name="redirect" value="58c6ace5c2af26ac04772d20aea67659590b9441/profile">

Now, we need to require all redirect parameters to be hashed using the config option.

In our craft/config/general.php file we add the following item:


'validateUnsafeRequestParams' => true,

And now when we go to submit our form, it’ll validate the hash we pass as the redirect parameter and process the request.

If for some reason we have this enabled but we don’t hash our redirect parameter, Craft will return a error.

Protecting Redirect Parameters in Craft 3

Protect the redirect parameter in Craft 3 is a simpler process because Craft 3 requires hashing of all redirect parameters. The validateUnsafeRequestParams config setting we have in Craft 2 is gone. You can’t opt in (and you can’t opt out).

The only step we need to do in Craft 3 is to set our redirect hidden field in the form we’re using and pass it through the hash filter.


<input type="hidden" name="redirect" value="{{ '/profile' |  hash}}">

And what happens in Craft 3 if don’t hash the redirect parameter?

In the case of the login form, the login routine will proceed (successful if the username and password are correct) but the redirect will fail. The value of the redirect parameter will not be honored.

The Takeaway

Whether we are using Craft 2 or 3, we should be hashing the redirect parameters. It’s a good security practice in Craft 2 and by doing so you’ll be making your sites even more ready for a Craft 3 upgrade.

Global Sets in the Craft Element API

by Ryan Irelan

This article covers both Craft 2 and Craft 3. The Craft 3 changes are at the bottom of the article.

I’ve used the Element API in Craft CMS to share data with a Vue.js app, create a headless CMS for a blog, and to share common information among a group of related sites.

The Element API makes it easy to expose your Craft-powered website’s data via JSON so other applications can consume it and use it.

The third example is what we’ll work on in this article.

I have a network of related sites that all have the same footer containing a list of links (for a “network” of sites). I want to manage this set of links with the Craft CMS installation of the anchor site and share the links with the other sites in the network.

I’m storing this set of links and display information (like headers and new column indicators) in a Global Set called Network (with the handle network).

The Network Global Set has a single Matrix field called Sites (sites) with this setup:

  • Site (site)
    • Site Name (siteName), Plain Text
    • Site URl (siteUrl), Plain Text
  • Section Header (sectionHeader)
    • Section Title (sectionTitle), Plain Text
  • Column (column)
    • New Column (newColumn), Lightswitch

I’m organizing the Sites into sections (via a Section Header). I can control when a new column is created using g the New Column Lightswitch field. I want to ensure the same exact layout across all sites, so it made the most sense to include layout data in the Global Set.

In my video lesson on the Element API, I only worked with Entries. In this example we need to expose the data in a Global Set. The approach is similar but instead of using an Entries element type we use a GlobalSet element type.

Let’s code the Element API and see how it works.

Coding the Element API for Global Sets

If you’re not familiar with the basics of using Element API in Craft, please first review my video lesson on the topic.

We start with a basic Element API wrapper:


<?php
namespace Craft;

return [
    'endpoints' => [
    ]
];

We define our first endpoint. It can be whatever we want, but let’s call it api/network.json.


<?php
    namespace Craft;
    return [
        'endpoints' => [
            'api/network.json' =>
            [
                    
            ],
                
        ]
    ];

We need to decide what we want to return. Our network data is in a Global Set, so we need to set the elementType for this endpoint to ElementType::GlobalSet.

Then set the criteria for retrieving the Global Set by defining the handle of the set we want.

   
<?php
    namespace Craft;
    return [
        'endpoints' => [
          'api/network.json' =>
          [
            'elementType' => ElementType::GlobalSet 
            'criteria' => ['handle' => 'network'],
          ]
                
        ]
    ];

Now that we have our data, we need to transform it into a format that is acceptable as JSON. To do that we use a transformer. In the transformer, we create an anonymous function which passes in the GlobalSetModel so Craft knows the data model it should use.

   
<?php
    namespace Craft;
    return [
        'endpoints' => [
          'api/network.json' =>
          [
            'elementType' => ElementType::GlobalSet 
            'criteria' => ['handle' => 'network'],
            'transformer' => function(GlobalSetModel $globalSet) 
            {
                // our data here
            }
          ]
                
        ]
    ];

Inside of the transformer function we define our data as we want it to appear in the JSON output.

   
<?php
    namespace Craft;
    return [
        'endpoints' => [
          'api/network.json' =>
          [
            'elementType' => ElementType::GlobalSet 
            'criteria' => ['handle' => 'network'],
            'transformer' => function(GlobalSetModel $globalSet) 
            {
              $networkBlocks = [];
              foreach ($globalSet->sites as $block) {
                switch ($block->type->handle) {
                  case 'site':
                    $networkBlocks[] = [ 
                     array(
                       'siteName' => $block->siteName,
                       'siteUrl' => $block->siteUrl,
                     )
                    ];
                  break;
                  case 'sectionHeader':
                    $networkBlocks[] = [
                      'sectionTitle' => $block->sectionTitle,
                     ];
                  break;
                  case 'column':
                    $networkBlocks[] = [
                      'newColumn' => $block->newColumn,
                    ];
                }   
              }

            }
          ]
                
        ]
    ];

We define an empty array called $networkBlocks and then we fill up this array with our data.

At foreach ($globalSet->sites as $block) { we iterate over each site row in the Matrix field. For each one we are checking the block type using a switch statement.

If the block has more than one field, we assign its data to a nested array because we want those values to stick together. The keys we choose (e.g. siteName and siteUrl) will be used in the JSON.

We add the single field blocks to the main array with the keys set to the name we want to appear in the JSON.

To get our newly transformed data out, we need to return the array from the transformer function.

   
<?php
    namespace Craft;
    return [
        'endpoints' => [
          'api/network.json' =>
          [
            'elementType' => ElementType::GlobalSet 
            'criteria' => ['handle' => 'network'],
            'transformer' => function(GlobalSetModel $globalSet) 
            {
              $networkBlocks = [];
              foreach ($globalSet->sites as $block) {
                switch ($block->type->handle) {
                  case 'site':
                    $networkBlocks[] = [ 
                     array(
                       'siteName' => $block->siteName,
                       'siteUrl' => $block->siteUrl,
                     )
                    ];
                  break;
                  case 'sectionHeader':
                    $networkBlocks[] = [
                      'sectionTitle' => $block->sectionTitle,
                     ];
                  break;
                  case 'column':
                    $networkBlocks[] = [
                      'newColumn' => $block->newColumn,
                    ];
                }   
              }

              return ['network' => $networkBlocks];

            }
          ]
                
        ]
    ];

Let’s test the API and see what we get. If we coded it properly, we should see something like this at, for example, yoursite.com/api/network.json:



    {
      "data": [
        {
          "network": [
            {
              "sectionTitle": "Insight"
                },
                [
                    {
                        "siteName": "Personal Blog",
                        "siteUrl": "http://ryanirelan.com/"
                    }
                ],
                    {
                        "sectionTitle": "Training"
                    },
                [
                    {
                        "siteName": "Mijingo",
                        "siteUrl": "https://mijingo.com"
                    }
                ],
                [
                    {
                        "siteName": "Up and Running with SVG",
                        "siteUrl": "http://svgtutorial.com"
                    }
                ],
                [
                    {
                        "siteName": "Craft CMS Essentials",
                        "siteUrl": "http://craftcmsessentials.com"
                    }
                ],
                {
                    "newColumn": "1"
                },
                {
                    "sectionTitle": "Running"
                },
                [
                    {
                        "siteName": "Ryan Runs",
                        "siteUrl": "http://www.ryanruns.com"
                    }
                ],
            ]
        }
      ],
      "meta": {
    "pagination": {
      "total": 1,
      "count": 1,
      "per_page": 100,
      "current_page": 1,
      "total_pages": 1,
      "links": []
    }
      }
    }

With our Global Set data exposed as JSON via the Element API, we’re now ready to pull it into our target sites.

Making it Work in Craft 3

To get this same approach to work in Craft 3 (in RC1 as a I write this), we need to make a couple adjustments.

First, the file name inside of the config directory should be element-api.php instead of the elementapi.php name in Craft 2.

Second, at the top of the file we no longer need to specify the Craft namespace but instead specify the element type (or types) class name we plan to access. In our case we only need to access a Global Set, so the top of our file looks like this:


<?php
  use craft\elements\GlobalSet;

If we also want to fetch and return entries we’d would need to specify the Entry class name, too.

Next, we need to change where we refer to the Element Type and replace that with the Element Type class name we just added.


<?php
    use craft\elements\GlobalSet;
    return [
            'endpoints' => [
                'api/network.json' =>
                [
                    'elementType' => GlobalSet::Class
                    'criteria' => ['handle' => 'network'],
                    'transformer' => function(GlobalSet $globalSet) 
                    {

We also change the transformer to use GlobalSet instead of GlobalSetModel.

Everything else should work as-is!

If you need some assistance with Craft 3’s implementation of the Element API, you can refer to the official documentation.

11 Things You Need to Know About Craft 3

by Ryan Irelan

There are dozens of changes to Craft in version 3 but below I’ve compiled the eleven things you need to know to have a smooth transition from Craft 2 to Craft 3.

Ready? Let's go.

1. Installable via Composer

If you installed Craft 3 during the beta period then you will know that Craft can now be installed with Composer. A nuisance to some but a big step up for others, Composer will make it easy to manage dependencies in Craft.

So how do you install Composer? This video walks through the process:


Note: The final release of Craft 3 will allow a non-Composer installation.

After you have Composer installed, you are ready to install Craft 3.

Follow the instructions in this video to get your first Craft 3 installation going:


Need help upgrading a Craft 2 site to Craft 3? Watch this video

2. Uses Twig 2

Craft 3 uses Twig 2. This update to Twig is, for the most part, a clean-up release. Here are a few important things to know about Twig 2 with regards to Craft:

  • Twig 2 Requires PHP 7 (and so does Craft 3)
  • Your current templates should work as-is with Twig 2. There is some deprecated template code but that’s related to Craft services, not Twig.
  • Twig slightly changed how macros are handled. All macros defined in-template must be imported explicitly in each template in which you want to use them (including “included” templates). See the docs for more information on macros.

3. Access to Craft service APIs in your templates

As of Craft 3 you now have access to the Craft service APIs right inside of your templates. Previously, Craft made available some special template functions that made some of the service APIs available.

Many of those template functions are going away in Craft 3 in favor of using the new service API access.

These template functions were mostly just Twig variables that exposed the data via an existing function. So instead of doing that we just get direct access to the function itself.

There is no PHP allowed in Craft templates, so previously if you wanted to access something in the backend of Craft you had to use a plugin.

Using a plugin is still a good idea if you have a significant amount of features you need to achieve, but for simple one-off things, having access to the Services inside of your template is going to be a game changer.

Here’s a look at how you can use this new functionality in your templates:


4. Multi-site Support

Another new feature in Craft 3 is multi-site. This was a popular feature when it was first announced but there was some confusion about it so let’s clear things up.

The Craft 3 multi-site feature allows you to handle multiple sites of content right from the Craft control panel. This could be a multi-language site, where there’s a different version of the site for different languages, or multiple sites with similar content (like micro sites for product lines).

Note: this feature is an enhancement of and replaces Locales in Craft 2.

Here's a walk-thru of using the new multi-site feature:


5. Better Debugging

The Yii Debug Toolbar is a new feature in Craft 3 that allows you to have a constant toolbar at the bottom of your front-end and control panel pages.

This toolbar gives you access to important debugging information including items that aren’t specific to Craft.

Here’s how it works and how you enable it:


6. Command Line Interface

When you are done installing Craft with Composer, you'll notice a little message at the end, just before the new command prompt, encouraging you to use the Craft setup command.

Craft 3 offers a new way to set up the site, which replaces the normal set up you do via the web browser.

So, why is this important?

  • Keeps you right in the terminal after installing
  • Possible to script the setup process--maybe part of a bigger routine where you're installing and setting up a new installation.
  • It automates the populating of your .env file for the site's database connection.

The craft CLI offers several options, but we're only going to concern ourselves right now with setup command.

Here’s how it works:


7. Hash Redirect Parameters

Back in version 2.5, Pixel & Tonic introduced a hash filter and later in a 2.6 release the ability to require hashing of the redirect parameters that are set in forms (like when you sign in or sign up).

They introduced this enhancement to make Craft more secure. There is a potential Denial of Service security issue with in -the-clear form data. In the example of the redirect parameter, this could allow someone to redirect the user to something other than what is specified in the hidden form element.

In Craft 3, however, the hashing of redirect parameters is required.

Here’s an overview of how that works:


8. Always use .all()

In Craft 2, we might do something like:

{% for entry in craft.entries.section('news') %}
       {{ entry
.title }}
    {
% endfor %

We iterate over the element query directly. Behind the scenes Craft knew we wanted that actual data so it calls find() for us automatically.

Based on a problem discovered with Twig’s loop variables and Craft, the Craft team decided to just force the usage of .all() and deprecate the usage of iterating over the element query without explicitly calling for the results.

What does this mean?

We need to now use all() on every element query in Craft 3. If you don’t it will still work but you’ll receive a deprecation error in your log (and it’ll break in Craft 4).

So the above code is now:

{% for entry in craft.entries.section('news').all() %}
       {{ entry
.title }}
    {
% endfor %

Here’s a hands-on review of this:


9. Use one() instead of first()

In Craft 3 the first() method to execute an Element Query is now deprecated in favor of one(). The new method will return the same thing as first() (the first matching element) and also return null if there are no matches.

So, this code:

{set entry craft.entries.section('news').first() %}
    {{ entry
.title }} 

Will now need to be written as:

{set entry craft.entries.section('news').one() %}
    {{ entry
.title }} 

10. Support for PostgreSQL

You hatin’ on MySQL or maybe your systems admininistrator thinks PostgreSQL is better? Now you can run Craft off a PostgreSQL database server.

11. Remote Assets are Now Plugins

Remote Assets Volumes (they’re called Volumes in Craft 3) now require a free, third-party plugin. The default installation of Craft only includes Local Volume support.

Until the Craft Plugin Store is available, you can download the Remote Volumes plugins from their Github repositories:

Is that it?

Well, that’s 11. But if you want even more here’s a big list of changes from Craft 2 to Craft 3.

What is a bare Git repository?

by Ryan Irelan

The standard way of initializing a new Git repository is to run git init. The directory in which you do this will be become the Working Tree for the repository.

As part of the initialization process, Git creates a .git directory (which his hidden by default because of the . in the name) that contains the repository itself. This is brains of the repository; it's where Git tracks your changes, stores commit objects, refs, etc. You probably only rarely interact with that hidden directory.

Okay, so all of this is to lay the groundwork for understanding a bare Git repository. What the heck is it?

A bare Git repository is a repository that is created without a Working Tree. Go ahead and create one to see.

git init --bare .

Run ls on that directory and you won't see a Working Tree but just the contents of what is typically in the .git directory.

Why this setup?

A bare Git repository is typically used as a Remote Repository that is sharing a repository among several different people. You don't do work right inside the remote repository so there's no Working Tree (the files in your project that you edit), just bare repository data.

And that's it.

Git the Essentials

Learn everything you need to be proficient in Git. 40+ videos, 6 hours of learning, a better understanding of Git.

Get Git Essentials

What is a Git Remote Repository?

by Ryan Irelan

By default Git is completely local to your computer. You can optionally have remote copies of the repository (either on a central server or service—like Github—or on a co-workers computer).

To get your copy of the repository up to a remote server you use the command git-push. If you want to retrieve others’ changes to the repository you use git-pull.

A Remote Repository in Git is a special type of repository in that it doesn't have a Working Tree. This is different than your local repository, which has your project files and then a hidden .git directory.

You can host your own Git remote repository or use one the popular online services, like Github, Gitlab, or Bitbucket.

Git the Essentials

Learn everything you need to be proficient in Git. 40+ videos, 6 hours of learning, a better understanding of Git.

Get Git Essentials