How to Redirect a User After Login or Registration


In this video we are going to look at how we can redirect our Users after they have either logged in, or just registered on our site.

Interestingly, both of these situations - log in, and registration - likely already have the desired redirection in place without you needing to configure anything.

Here is what happens by default:

If a User is trying to access a page that requires log in, and they haven't logged in yet, then once they have successfully logged in they will be redirected back to the original page they were requesting.

If a User is trying to access a page that requires log in, and they haven't registered yet, then once they have successfully registered they will have a clickable link allowing them to head back to the original page they were requesting before they registered.

Being Symfony, we are of course, free to customise and tweak this flow to meet whatever needs we have.

Most of the changes we are going to make are not specific to FOSUserBundle. These options apply to any form_login config we may need to set up. The only difference with FOSUserBundle will be in our Registration flow.

If you are unsure of any of the available configuration options then be sure to check out the Symfony Security Bundle Configuration reference.

The section we are interested in for the purposes of this video is the form_login section:

# app/config/security.yml
security:
    firewalls:            # Required
        somename:
            form_login:
                # submit the login form here
                check_path: /login_check

                # the user is redirected here when they need to log in
                login_path: /login

                # if true, forward the user to the login form instead of redirecting
                use_forward: false

                # login success redirecting options (read further below)
                always_use_default_target_path: false
                default_target_path:            /
                target_path_parameter:          _target_path
                use_referer:                    false

                # login failure redirecting options (read further below)
                failure_path:    /foo
                failure_forward: false
                failure_path_parameter: _failure_path
                failure_handler: some.service.id
                success_handler: some.service.id

                # field names for the username and password fields
                username_parameter: _username
                password_parameter: _password

                # csrf token options
                csrf_parameter: _csrf_token
                intention:      authenticate
                csrf_provider:  my.csrf_provider.id

                # by default, the login form *must* be a POST, not a GET
                post_only:      true
                remember_me:    false

                # by default, a session must exist before submitting an authentication request
                # if false, then Request::hasPreviousSession is not called during authentication
                # new in Symfony 2.3
                require_previous_session: true

One thing that may not be immediately obvious is that the provided locations can be in the form of a URI, an absolute URL, or a Symfony route name.

Huh?

An example better illustrates this:

# app/config/security.yml
security:
    firewalls:            
        somename:
            form_login:
                # One of the following:
                default_target_path:            /some-page/on/our-website
                # default_target_path:          http://www.bbc.co.uk
                # default_target_path:          a_symfony_route_name

All are valid options. You can only choose one though, which is why the other two are commented out.

An illustration of this would be in the previous video on Translations where we had to provide the FOSUserBundle route names for our login_path and check_path etc after adding the locale to the URL.

Default Target Path

Perhaps one of the more confusing parts of this setup is the default_target_path option, which you may think would always send our users to a given path on successful login, given that it is the default.

This can be the case, if you set always_use_default_target_path to true. It defaults to false.

My question at this point - why would you have two options seemingly for the same thing?

In answer to that, I find it easiest to show the code. The default_target_path will be used if Symfony cannot determine where to send the user by any other means:

// /vendor/symfony/symfony/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php

    /**
     * Builds the target URL according to the defined options.
     *
     * @param Request $request
     *
     * @return string
     */
    protected function determineTargetUrl(Request $request)
    {
        if ($this->options['always_use_default_target_path']) {
            return $this->options['default_target_path'];
        }

        if ($targetUrl = $request->get($this->options['target_path_parameter'], null, true)) {
            return $targetUrl;
        }

        if (null !== $this->providerKey && $targetUrl = $request->getSession()->get('_security.'.$this->providerKey.'.target_path')) {
            $request->getSession()->remove('_security.'.$this->providerKey.'.target_path');

            return $targetUrl;
        }

        if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
            return $targetUrl;
        }

        return $this->options['default_target_path'];
    }

Notice, this code snippet also shows the implementation of what would happen if use_referer were set to true in your config:

# app/config/security.yml
security:
    firewalls: 
        somename:
            form_login:
                use_referer: true

By default, use_referer is false. If set to true, the Referer header would be checked:

'HTTP_REFERER' The address of the page (if any) which referred the user agent to the current page. This is set by the user agent. Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. In short, it cannot really be trusted.

(from the PHP.net docs)

The most likely situation where you default_target_path is going to come in to play - that I can think of at least - is when a User of your site bookmarks your /login route. They then visit your site using this bookmark, and have no prior set location to return to.

At this point, you want this User to be sensibly logged back in and directed to the root of your site - http://yoursite.com/ - so you might set default_target_path: / or use the Symfony route name for the controller action that handles the site root.

Registration Flow

What is perhaps less likely to be on your mind is the lesser travelled path - the path of a new User registering on your site for the first time.

As described earlier, in this instance, depending on the page the User was on before they registered, depends on whether they will see a link to let them return to the previous page once signed in.

This step is FOSUserBundle specific, and relies on a little Twig template logic to do so:

<!-- /app/Resources/FOSUserBundle/views/Registration/confirmed.html.twig -->
{% extends "FOSUserBundle::layout.html.twig" %}

{% trans_default_domain 'FOSUserBundle' %}

{% block fos_user_content %}
    <p>{{ 'registration.confirmed'|trans({'%username%': user.username}) }}</p>
    {% if targetUrl %}
    <p><a href="{{ targetUrl }}">{{ 'registration.back'|trans }}</a></p>
    {% endif %}
{% endblock fos_user_content %}

Note here we are checking for the presence of a targetUrl. This is determined inside the FOSUserBundle Registration controller:

// /vendor/friendsofsymfony/user-bundle/Controller/RegistrationController.php

   /**
     * Tell the user his account is now confirmed
     */
    public function confirmedAction()
    {
        $user = $this->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        return $this->render('FOSUserBundle:Registration:confirmed.html.twig', array(
            'user' => $user,
            'targetUrl' => $this->getTargetUrlFromSession(),
        ));
    }

    private function getTargetUrlFromSession()
    {
        // Set the SecurityContext for Symfony <2.6
        if (interface_exists('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')) {
            $tokenStorage = $this->get('security.token_storage');
        } else {
            $tokenStorage = $this->get('security.context');
        }

        $key = sprintf('_security.%s.target_path', $tokenStorage->getToken()->getProviderKey());

        if ($this->get('session')->has($key)) {
            return $this->get('session')->get($key);
        }
    }

The target_path is part of the key that Symfony will use behind the scenes to store your last requested URI in your session:

By default, the Security component retains the information of the last request URI in a session variable named _security.main.target_path (with main being the name of the firewall, defined in security.yml). Upon a successful login, the user is redirected to this path, as to help them continue from the last known page they visited.

(taken from the Cookbook entry on Target Path)

The main point to take away from this video is that likely Symfony (and FOSUserBundle) will behave as you want it to, without any effort on your part.

If you need to change things, Symfony's security component is configurable enough to let you do so quickly and easily.

Largely FOSUserBundle improves upon what is already implemented for us with the Symfony2 framework. However, in the case of our User Registration flow, it does add in a little extra custom logic that we can tweak, and this flow is specific to FOSUserBundle.

Code For This Course

Get the code for this course.

Episodes