dotkernel / dot-authentication-web

Login/logout flow for web applications to use with dot-authentication service
MIT License
2 stars 3 forks source link

[!CAUTION]

Security-Only Maintenance Mode

This package is considered feature-complete, and is now in security-only maintenance mode.

OSS Lifecycle GitHub license PHP from Packagist (specify version)

dot-authentication-web

This package provides a simple login/logout flow for web applications built on top of dot-authentication. It relies on events to do the authentication process offering in the same time flexibility for customization and further extension.

Installation

Install the package by running the following command in your project root directory

$ composer install dotkernel/dot-authentication-web

Enable the module by merging its ConfigProvider output to your application's autoloaded configuration. This registers all required dependencies, so you don't have to do it manually.

Configuration and usage

First of all, LoginAction, LogoutAction, and UnauthorizedHandler must be registered in the middleware stack. The first two are routed middleware, and the last one is an error handler(still a middleware) that handles the \Dot\Authentication\Exception\UnauthorizedException.

routes.php

Here is an example configuration for this module, you can put this in config/autoload or in a ConfigProvider in your project. It is based on the above configured middleware

authentication-web.global.php
return [
    'dot_authentication' => [
        'web' => [
            // login/logout route definitions, as configured in the expressive router
            'login_route' => ['route_name' => 'login', 'route_params' => []],
            'logout_route' => ['route_name' => 'logout', 'route_params' => []],

            //template name to use for the login form
            'login_template' => 'app::login',

            //where to redirect after login success
            'after_login_route' => ['route_name' => 'account', 'route_params' => []],

            //where to redirect after logging out
            'after_logout_route' => ['route_name' => 'login', 'route_params' => []],

            //enable the wanted url feature, to go to the previously requested uri after login
            'enable_wanted_url' => true,
            'wanted_url_name' => 'redirect',

            // event listeners for authentication, logout and unauthorized events
            'event_listeners' => [
                [
                    'type' => 'Some\Class\Or\Service',
                    'priority' => 1
                ],
            ],

            //for overwriting default module messages
            'messages_options' => [
                'messages' => [
                    // see \Dot\Authentication\Web\Options\MessageOptions class
                ],
            ],
        ]
    ]
];

Login flow

Happens in the LoginAction middleware. On a GET request, it renders the HTML template configured as above, at login_template configuration key. The login process happens on POST requests. The login page should display a login form, with its action going back to the login route via method POST. Note that the LoginAction middleware on its own, does not know about any login form, nor does validate the POST data. It alows customization though, through before and after authentication events, which will see later.

It uses the authentication service to authenticate the request. Depending on the authentication service implementation, additional actions might be needed before, which can be done in pre-authentication event. In case you use dot-authentication-service, along with the CallbackCheck adapter, the request should be injected beforehand with a DbCredential object attribute for example.

If any error occur, the middleware will do a PRG redirect to the login route, using the flash messenger(see dot-flashmessenger) to set a session error message which you can display in the login template.

In case authentication is successful, it will trigger the after authentication event, and will redirect to the after_login_route as configured. If it detects a wanted_url in the query parameters, it will redirect there instead. This is useful if the application redirected to the login page due to an unauthorized exception, setting the wanted url. After successful login, user will be redirected to the desired original page.

Authentication events

An authentication event, be it login, logout or unauthorized event, is represented by the AuthenticationEvent class. The events regarding strictly the authentication process are

class AuthenticationEvent extends Event
{
    const EVENT_BEFORE_AUTHENTICATION = 'event.beforeAuthentication';
    const EVENT_AFTER_AUTHENTICATION = 'event.afterAuthentication';
    const EVENT_AUTHENTICATION_SUCCESS = 'event.authenticationSuccess';
    const EVENT_AUTHENTICATION_ERROR = 'event.authenticationError';
    const EVENT_AUTHENTICATION_BEFORE_RENDER = 'event.authenticationBeforeRender';

    //...
AuthenticationEvent::EVENT_BEFORE_AUTHENTICATION
AuthenticationEvent::EVENT_AFTER_AUTHENTICATION
AuthenticationEvent::EVENT_AUTHENTICATION_SUCCESS
AuthenticationEvent::EVENT_AUTHENTICATION_ERROR
AuthenticationEvent::EVENT_AUTHENTICATION_BEFORE_RENDER

As you can see, listening to authentication events allows you to inject additional logic into the login process. It also allows you to do it in a more decoupled way. For a full understanding of the entire process, make sure to check the LoginAction class. You can also find the frontend and admin applications useful, as they already provide some customization. Check the corresponding authentication event listeners defined there, for a sample of what you can achieve through listeners.

Logout flow

The logout process is much simpler. It triggers 2 events: after and before logout. In between, the authenticated identity is cleared using the clearIdentity() method of the authentication service. After that, the client is redirected the the configured after_logout_route.

Logout events

AuthenticationEvent::EVENT_BEFORE_LOGOUT
AuthenticationEvent::EVENT_AFTER_LOGOUT

UnauthorizedException handling

Mezzio error handlers are middleware that wraps the response in a try-catch block. They are registered early in the pipeline, in order to get all possible exceptions. This package's UnauthorizedHandler handles the following exceptions

For any other kind of exceptions, it re-throws them in order to be handles by other error handlers.

Events

When an unauthorized exception is catched, the following steps are followed by the error handler

AuthenticationEvent::EVENT_UNAUTHORIZED

You can listen to this event mainly for logging purposes or additional actions after this kind of exception. You can also return a ResponseInterface from one of the event listeners(the event chain will stop), in which case, that response will be returned to the client as-is, basically overwriting the entire error handling process.

Writing an authentication listener

Authentication listeners must implement AuthenticationEventListenerInterface, an interface that defines all possible event method handlers. You should also extend the AbstractAuthenticationEventListener or use the AuthenticationEventListenerTrait which are already supporting the event attach methods. They also implement the event listener interface, by providing empty interface methods. This helps when writing your event listener, as you may want to listen to only some of the events. This will let you implement just the event handler methods that you are interested in.

AuthenticationEventListenerInterface.php
// the authentication event listener interface defined in this package
interface AuthenticationEventListenerInterface extends ListenerAggregateInterface
{
    public function onBeforeAuthentication(AuthenticationEvent $e);

    public function onAfterAuthentication(AuthenticationEvent $e);

    public function onAuthenticationSuccess(AuthenticationEvent $e);

    public function onAuthenticationError(AuthenticationEvent $e);

    public function onAuthenticationBeforeRender(AuthenticationEvent $e);

    public function onBeforeLogout(AuthenticationEvent $e);

    public function onAfterLogout(AuthenticationEvent $e);

    public function onUnauthorized(AuthenticationEvent $e);
}
MyAuthenticationEventListener.php
//...
class MyAuthenticationEventListener extends AbstractAuthenticationEventListener
{
    public function onBeforeAuthentication(AuthenticationEvent $e)
    {
        // do something...
    }

    // other event handlers methods
}
return [
    'dot_authentication' => [
        'web' => [
            //...

            // event listeners for authentication, logout and unauthorized events
            'event_listeners' => [
                [
                    'type' => MyAuthenticationEventListener::class,
                    'priority' => 1
                ],
            ],

           //....
        ]
    ]
];