Buy
Buy

Target Path: Redirecting an Anonymous User

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

After changing the access_control back to ROLE_ADMIN:

security:
... lines 2 - 40
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
... lines 43 - 44

If we try to access /admin/comment again, we see that same "Access Denied" page: 403 forbidden.

Customizing the Error Page

Like with all the big, beautiful error pages, these are only shown to us, the developers. On production, by default, your users will see a boring, generic error page that truly looks like it was designed by a developer.

But, you can - and should - customize this. We won't go through it now, but if you Google for "Symfony error pages", you can find out how. The cool thing is that you can have a different error page per status code. So, a custom 404 not found page and a different custom 403 "Access Denied" page - with, ya know, like a mean looking alien or something to tell you to stop trying to hack the site.

Redirecting Anonymous Users: Entry Point

Anyways, I have a question for you. First, log out. Now that we are anonymous: what do you think will happen if we try to go to /admin/comment? Will we see that same Access Denied page? After all, we are anonymous... so we definitely do not have ROLE_ADMIN.

Well... let's find out! No! We are redirected to the login page! That's... awesome! If you think about it, that's the exact behavior we want: if we're not logged in and we try to access a page that requires me to be logged in, we should totally be sent to the login form so that we can login.

The logic behind this actually comes from our authenticator. Or, really, from the parent AbstractFormLoginAuthenticator. It has a method - called start() - that decides what to do when an anonymous user tries to access something. It's called an entry point, and we'll learn more about this later when we talk about API authentication.

Redirecting Back on Success

But for now, great! Our system already behaves like we want. But now... check this out. Log back in with [email protected], password engage. When I hit enter, where do you think we'll be redirected to? The homepage? /admin/comment? Let's find out.

We're sent to the homepage! Perfect, right? No, not perfect! I originally tried to go to /admin/comment. So, after logging in, to have a great user experience, we should be redirected back there.

The reason that we're sent to the homepage is because of our code in LoginFormAuthenticator. onAuthenticationSuccess() always sends the user to the homepage, no matter what:

... lines 1 - 18
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 21 - 71
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->router->generate('app_homepage'));
}
... lines 76 - 80
}

Hmm: how could we update this method to send the user back to the previous page instead?

Symfony can help with this. Find your browser, log out, and then go back to /admin/comment. Whenever you try to access a URL as an anonymous user, before Symfony redirects to the login page, it saves this URL - /admin/comment - into the session on a special key. So, if we can read that value from the session inside onAuthenticationSuccess(), we can redirect the user back there!

To do this, at the top of your authenticator, use a trait TargetPathTrait:

... lines 1 - 17
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
... lines 23 - 87
}

Then, down in onAuthenticationSuccess(), add if $targetPath = $this->getTargetPath(). This method comes from our handy trait! It needs the session - $request->getSession() - and the "provider key", which is actually an argument to this method:

... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
... line 78
}
... lines 80 - 81
}
... lines 83 - 87
}

The provider key is just the name of your firewall... but that's not too important here.

Oh, and, yea, the if statement might look funny to you: I'm assigning the $targetPath variable and then checking to see if it's empty or not. If it's not empty, if there is something stored in the session, return new RedirectResponse($targetPath):

... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
... lines 80 - 81
}
... lines 83 - 87
}

That's it! If there is no target path in the session - which can happen if the user went to the login page directly - fallback to the homepage:

... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->router->generate('app_homepage'));
}
... lines 83 - 87
}

Let's try it! Log back in... with password engage. Yea! Got it! I know, it feels weird to celebrate when you see an access denied page. But we expected that part. The important thing is that we were redirected back to the page we originally tried to access. That's excellent UX.

Next - as nice as access controls are, we need more granular control. Let's learn how to control user access from inside the controller.

Leave a comment!

  • 2019-01-28 Diego Aguiar

    Hey Dan Meigs

    You need to set the new route into the user session by doing something like this:


    $request->getSession()->set('_security.main.target_path', $returnPath);

    Cheers!

  • 2019-01-26 Dan Meigs

    Hey guys,

    Redirecting after login works if the target path is listed the access_control part of my security.yaml. However, if I have a controller with the following code,

            
    $user = $this->getUser();
    if (null === $user) {
    return new RedirectResponse($this->container->get('router')->generate('app_login'));
    }


    The target path is null in my LoginFormAuthenticator.

    How do I set the target path before returning the redirect response?

    Thanks!

  • 2018-10-17 Victor Bocharsky

    Hey Peter,

    Yeah, probably it won't work out of the box, though you can read the "_target_path" using Request object in your Guard authenticator and do a proper redirect :)

    Cheers!

  • 2018-10-17 Peter Bowyer

    How can I specify the target path via a hidden input in my form? Every page has the login form embedded in it on my site, so there isn't the initial redirect to the Login page to set the target path. Once logged in I want to redirect back to the page the user was on.

    I've tried
    <input type="hidden" name="_target_path" value="{{ path('current_page') }}"/>
    but that doesn't work with Guard (I understand it's part of another login system in Symfony called `form_login` ¯\_(ツ)_/¯

  • 2018-10-08 Ahmad Mayahi

    Use the following code if you care about readability:


    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
    $targetPath = $this->getTargetPath($request->getSession(), $providerKey) ?:
    $this->router->generate("app_homepage");

    return new RedirectResponse($targetPath);
    }
  • 2018-09-24 Victor Bocharsky

    Hey guys,

    Thanks for mention it. Yeah, this is just a draft yet and looks like it was fixed already :)

    Cheers!

  • 2018-09-23 mickaël andrieu

    Already fixed then ^^

  • 2018-09-22 Gaston

    This content is the same as previous chapter