Cherrycake
ExamplesGithub
version 1.x beta
version 1.x beta
  • Introduction
  • Status
  • Changelog
  • Migration
  • Architecture
    • Basics
    • Modules
    • Classes
    • Lifecycle
      • Deep lifecycle
    • Performance
    • Security
    • Patterns
      • Files structure
    • Items
    • Server requirements
  • Guide
    • Getting started
      • Skeleton start
      • Docker start
    • Modules guide
    • Classes guide
    • Actions guide
      • Complex actions
      • Variable path components
      • Accept GET or POST parameters
      • Getting the URL of an action
      • Cached actions
      • Brute force attacks
    • Patterns guide
      • Passing variables to a pattern
      • Nested patterns
      • Cached patterns
    • Cache guide
      • Time To Live
      • Using cache
      • Lists
      • Queues
      • Pools
    • Database guide
      • Basic queries
      • Prepared queries
      • Cached queries
      • Cache key naming
      • Removing queries from cache
    • Items guide
      • Item cache
      • Item lists
      • Items custom filters
      • Items custom ordering
      • Mixing filters and ordering
      • Items with relationships
      • Items cache
    • HtmlDocument guide
    • Css and Javascript guide
      • Modules injecting CSS and JavaScript
    • Session guide
    • Login guide
      • Creating a complete login workflow
    • Locale guide
      • Multilingual texts
      • Domain based site localization
    • Log guide
      • Loading Log events from the database
    • Stats guide
      • Stats events with additional dimensions
      • Loading Stats events from the database
    • Janitor guide
      • Janitor tasks configuration files
    • Command line interface
    • Debugging
  • Reference
    • Core modules
      • Actions
        • Actions methods
      • Browser
      • Cache
        • Cache methods
      • Css
        • Css methods
      • Database
      • Email
      • Errors
      • HtmlDocument
        • HtmlDocument methods
      • ItemAdmin
      • Janitor
        • Janitor methods
      • Javascript
        • Javascript methods
      • Locale
        • Locale methods
      • Log
        • Log methods
      • Login
        • Login methods
      • Output
        • Output methods
      • Patterns
        • Patterns methods
      • Security
        • Security methods
      • Session
        • Session methods
      • Stats
        • Stats methods
      • SystemLog
      • TableAdmin
      • Validate
    • Core classes
      • Action
        • Action methods
        • Action properties
      • AjaxResponseJson
      • BasicObject
        • BasicObject methods
      • CacheProvider
        • CacheProvider methods
      • Color
      • DatabaseProvider
        • DatabaseProvider methods
      • DatabaseResult
        • DatabaseResult methods
        • DatabaseResult properties
      • DatabaseRow
      • Engine
        • Engine methods
        • Engine properties
      • Gradient
      • Item
        • Item methods
        • Item properties
      • Items
        • Items methods
        • Items properties
      • Image
      • JanitorTask
        • JanitorTask methods
        • JanitorTask properties
      • LogEvent
        • LogEvent methods
        • LogEvent Properties
      • LogEvents
        • LogEvents methods
      • Module
        • Module methods
        • Module properties
      • Response
      • Request
        • Request methods
      • RequestParameter
        • RequestParameter methods
      • RequestPathComponent
        • RequestPathComponent methods
      • Result
      • StatsEvent
        • StatsEvent properties
      • StatsEvents
        • StatsEvents methods
      • SystemLogEvent
        • SystemLogEvent methods
        • SystemLogEvent properties
      • SystemLogEvents
        • SystemLogEvents methods
  • Code conventions
  • License
  • Extras
Powered by GitBook
On this page
  • Adding a logout button
  • Encrypting user passwords

Was this helpful?

  1. Guide
  2. Login guide

Creating a complete login workflow

PreviousLogin guideNextLocale guide

Last updated 3 years ago

Was this helpful?

The module provides the logic for a user authentication mechanism, but it's up to you to build a form to ask the user for his credentials, or to implement any kind of interface for a user to authenticate in your web app.

We'll analyze step by step the example provided in the Cherrycake documentation examples , in the module called LoginGuide.

See this complete login workflow working in the site.

First we'll create our module in /src/LoginGuide/LoginGuide.class.php:

<?php

namespace CherrycakeApp\LoginGuide;

class LoginGuide extends \Cherrycake\Module {
    protected $dependentCoreModules = [
        "HtmlDocument",
        "Login"
    ];
}

See how we've already added the and dependencies.

Now we'll that will show a welcome page when the user visits the /login-guide URL:

<?php

namespace CherrycakeApp\LoguinGuide;

class LoginGuide extends \Cherrycake\Module {
    protected $dependentCoreModules = [
        "HtmlDocument",
        "Login"
    ];
    
    public static function mapActions() {
        global $e; 
        $e->Actions->mapAction(
            "loginGuideHome",
            new \Cherrycake\Actions\ActionHtml([
                "moduleType" => \Cherrycake\ACTION_MODULE_TYPE_APP,
                "moduleName" => "LoginGuide",
                "methodName" => "home",
                "request" => new \Cherrycake\Actions\Request([
                    "pathComponents" => [
                        new \Cherrycake\Actions\RequestPathComponent([
                            "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                            "string" => "login-guide"
                        ])
                    ]
                ])
            ])
        );
    }
    
    function home() {
        global $e;
    
        $e->Output->setResponse(new \Cherrycake\Actions\ResponseTextHtml([
            "code" => \Cherrycake\RESPONSE_OK,
            "payload" =>
                $e->HtmlDocument->header().
                ($e->Login->isLogged() ?
                    "You are logged in"
                :
                    "You are not logged in"
                ).
                $e->HtmlDocument->footer()
        ]));
    }
}

Now we'll add another action that will show a login form in the /login-guide/login-page URL:

$e->Actions->mapAction(
    "loginGuideLoginPage",
    new \Cherrycake\Actions\ActionHtml([
        "moduleType" => \Cherrycake\ACTION_MODULE_TYPE_APP,
        "moduleName" => "LoginGuide",
        "methodName" => "loginPage",
        "request" => new \Cherrycake\Actions\Request([
            "pathComponents" => [
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "login-guide"
                ]),
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "login-page"
                ])
            ]
        ])
    ])
);
function loginPage() {
    global $e;

    $e->Output->setResponse(new \Cherrycake\Actions\ResponseTextHtml([
        "code" => \Cherrycake\RESPONSE_OK,
        "payload" =>
            $e->HtmlDocument->header().
            "
                <form method=post>
                    <input name=email type=text name=email placeholder=\"Email\" />
                    <input name=password type=password name=password placeholder=\"Password\" />
                    <input type=submit value=\"Login\"/>
                </form>
            ".
            $e->HtmlDocument->footer()
    ]));
}

And we'll add a button right next to the You are not logged in message, that will link to this login page:

function home() {
    global $e;

    $e->Output->setResponse(new \Cherrycake\Actions\ResponseTextHtml([
        "code" => \Cherrycake\RESPONSE_OK,
        "payload" =>
            $e->HtmlDocument->header().
            ($e->Login->isLogged() ?
                "You are logged in"
            :
                "You are not logged in".
                "<a href=\"{$e->Actions->getAction("loginGuideLoginPage")->request->buildUrl()}\" class=button>Login</a>"
            ).
            $e->HtmlDocument->footer()
    ]));
}

So now, when we access the /login-guide page, this appears:

And when we click the Login button, the /login-guide/login-page appears:

Clicking the Login button does nothing because we haven't yet set the action property of the form HTML element.

Now let's create an action that will be triggered when the Login button is clicked. This action will use the Login module to check the received email and password, and act accordingly afterwards.

We'll call this action loginGuideDoLogin:

$e->Actions->mapAction(
    "loginGuideDoLogin",
    new \Cherrycake\Actions\ActionHtml([
        "moduleType" => \Cherrycake\ACTION_MODULE_TYPE_APP,
        "moduleName" => "LoginGuide",
        "methodName" => "doLogin",
        "request" => new \Cherrycake\Actions\Request([
            "isSecurityCsrf" => true,
            "pathComponents" => [
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "login-guide"
                ]),
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "do-login"
                ])
            ]
        ]),
        "isSensibleToBruteForceAttacks" => true
    ])
);

Now we need to modify the login form in the loginPage method to make it request this loginGuideDoLogin action URL whenever its posted. We've already seen how to retrieve the URL of an action above. In this case, we'll do it like this: $e->Actions->getAction("loginGuideDoLogin")->request->buildUrl();

And this is how it looks when implemented in the form:

"
    <form method=post action=\"{$e->Actions->getAction("loginGuideDoLogin")->request->buildUrl()}\">
        <input name=email type=text name=email placeholder=\"Email\" />
        <input name=password type=password name=password placeholder=\"Password\" />
        <input type=submit value=\"Login\"/>
    </form>
"

And here's how the doLogin method looks:

function doLogin($request) {
    global $e;
    $result = $e->Login->doLogin($request->email, $request->password);
    if (
        $result == \Cherrycake\Login\LOGIN_RESULT_FAILED_UNKNOWN_USER
        ||
        $result == \Cherrycake\Login\LOGIN_RESULT_FAILED_WRONG_PASSWORD
    ) {
        $e->Output->setResponse(new \Cherrycake\Actions\ResponseTextHtml([
            "code" => \Cherrycake\RESPONSE_OK,
            "payload" => $e->HtmlDocument->header()."Login error".$e->HtmlDocument->footer()
        ]));
    }
    else {
        $e->Output->setResponse(new \Cherrycake\Actions\Response([
            "code" => \Cherrycake\RESPONSE_REDIRECT_FOUND,
            "url" => $e->Actions->getAction("loginGuideHome")->request->buildUrl()
        ]));
    }
}

To login with the example users in the Cherrycake documentation examples skeleton database, you can use the following email/password combinations:

  • Douglas Engelbart

    • Email: doug@berkeley.edu

    • Password: TheMotherOfAllDemos413

  • John Horton Conway

    • Email: johnny@princeton.org

    • Password: lavidaloca

  • Frank Abagnale

    • Email: frank.abagnale@united.com

    • Password: catch_me_?_you_can

  • Carl Sagan

    • Email: carl@cosmos.org

    • Password: palebluedot34

  • Richard Feynmann

    • Email: ricky@mit.edu

    • Password: 137

Now, when you login with a correct email and password, you'll get redirected to the Login guide home, and the message You are logged in will be shown.

"You are logged in as {$e->Login->user->name}"

Adding a logout button

Let's add a logout button now. First, we'll create a new action to perform the logout operation, we'll call it loginGuideLogout, and it will be triggered with the /login-guide/logout URL:

$e->Actions->mapAction(
    "loginGuideLogout",
    new \Cherrycake\Actions\ActionHtml([
        "moduleType" => \Cherrycake\ACTION_MODULE_TYPE_APP,
        "moduleName" => "LoginGuide",
        "methodName" => "logout",
        "request" => new \Cherrycake\Actions\Request([
            "pathComponents" => [
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "login-guide"
                ]),
                new \Cherrycake\Actions\RequestPathComponent([
                    "type" => \Cherrycake\REQUEST_PATH_COMPONENT_TYPE_FIXED,
                    "string" => "logout"
                ])
            ]
        ])
    ])
);

Now, just like we did before, we add a logout button in the home method that will appear along the Your are logged in as ... message. This time, to get the logout URL we use $e->Actions->getAction("loginGuideLogout")->request->buildUrl();

function home() {
    global $e;

    $e->Output->setResponse(new \Cherrycake\Actions\ResponseTextHtml([
        "code" => \Cherrycake\RESPONSE_OK,
        "payload" =>
            $e->HtmlDocument->header().
            ($e->Login->isLogged() ?
                "You are logged in as {$e->Login->user->name}".
                "<a href=\"{$e->Actions->getAction("loginGuideLogout")->request->buildUrl()}\" class=button>Logout</a>"
            :
                "You are not logged in".
                "<a href=\"{$e->Actions->getAction("loginGuideLoginPage")->request->buildUrl()}\" class=button>Login</a>"
            ).
            $e->HtmlDocument->footer()
    ]));
}

And now we implement the logout method like this:

function logout() {
    global $e;
    $e->Login->logoutUser();
    $e->Output->setResponse(new \Cherrycake\Actions\Response([
        "code" => \Cherrycake\RESPONSE_REDIRECT_FOUND,
        "url" => $e->Actions->getAction("loginGuideHome")->request->buildUrl()
    ]));
}

Encrypting user passwords

echo $e->Login->encryptPassword("mypassword");
sha512:100000:I3Yrl8zztLfTj0Lu04rbNDsprLQKSz8Z:farmXBKJuxjcgrg9ELnDnZkKmzOHx5EL

Notice that the page shows the message You are logged in or You are not logged in. To determine whether the current user is logged in, we use the method.

See how, instead of linking directly to /login-guide/login-page, we've used the and the methods in chain to obtain the URL for the action that triggers the login page, as you've learned in the .

We've set the parameter to true when creating the to improve the resistance of this request to brute force attacks. We've also set the parameter to true when creating the , which adds protection against Cross-Site Request Forgery-type attacks to this request.

Note we've used the method to check the passed email and password. If the login failed, we show a simple error page. If it was successful, we redirect the user to the login home using with a code.

What if we wanted to show the user name there? Something like You are logged in as <user name>. Well, since the module does all the work of retrieving the logged user for you, as long as you have the module as a dependency in your modules, you'll be able to access the logged User object at any time via $e->Login->user. This is how we would modify the home method in our example to include the user name:

This calls the method and then redirects them to the login home page.

You're in charge of adding new users to your database and storing their credentials in the fields you've specified when .

The module uses by default a very secure salted password hashing mechanism which implements a method, a algorithm. This method is compliant with the PBKDF2 test vectors specified in the , and is based on the by from .

To generate a password hash to be stored in the database, use the method, like this:

Login
repository
Cherrycake documentation examples
HtmlDocument
Login
define an action
Login
Login
Login
Password-Based Key Derivation Function
Key stretching
RFC 6070
implementation
Taylor Hornby
Defuse.ca
Actions guide
Action
Request
creating your User class
Output::setResponse
isSensibleToBruteForceAttacks
Actions::getAction
Request::buildUrl
isSecurityCsrf
Login::isLogged
Login::logoutUser
Login::encryptPassword
Login::doLogin
RESPONSE_REDIRECT_FOUND