7

Whenever I go to /admin/logout, I'm correctly redirected to the root of my project but still logged in when I visit /admin/ as I'm not prompted for credentials.

Here is my configuration:

security.yml

security:
    firewalls:
        admin_area:
            pattern:    ^/admin
            http_basic: ~
            stateless:  true
            switch_user: { role: ROLE_SUPER_ADMIN, parameter: _want_to_be_this_user }
            logout: { path: /admin/logout, target: / }

AdminBundle/Resources/config/routing.yml

logout:
    pattern:   /logout

app/config/routing.yml

admin:
    resource: "@AdminBundle/Resources/config/routing.yml"
    prefix:   /admin

The authorization is still in place as the headers state Authorization:Basic YWRtaW46cEAkJHcwUmQh so I guess credentials are still provided to the application during the request.

I know there is no proper way to logout from a HTTP Basic Auth as per this question but maybe Symfony2 allows it?

6
  • 1
    possible duplicate of Symfony2 http_basic security configuration problem Commented Mar 24, 2015 at 9:38
  • try to change your logout pattern to /admin/logout so it is under a firewall also you can check if you havent checked remember me - try to remove cookies then login and logout Commented Mar 24, 2015 at 9:58
  • Thanks @Vardius for your comment but my pattern is already /admin/logout as I'm correctly redirected. Also, I haven't implemented the option remember_me (yet). Commented Mar 24, 2015 at 10:20
  • here in your post i can see that the logout in your routing.yml is not under the firewall. try to change that and then istead of patterns in your security.yml try to use route names as logout: path: logout.... Commented Mar 24, 2015 at 10:22
  • Actually, my routing routing.yml file is under the AdminBundle bundle which has its routes imported in app/config/routing.yml like so admin: resource: "@AdminBundle/Resources/config/routing.yml" prefix: /admin so the pattern of the route is /admin/logout so under the firewall. Changing the pattern in security.yml for the name of the route did nothing unfortunately (still logged in). Thanks for your help. Commented Mar 24, 2015 at 10:28

2 Answers 2

12

Once logged in via http auth, your browser will cache and add your login credentials to each subsequent request in the form of a header like this:

Authorization:Basic YWRtaW46YWRtaW4=

When you do a logout, the next request to the server will still hold your http credentials and log you in again.

So the trick is to lose the http credentials on the client side after destroying the session on the server side.

In the past there where some hackidy methods like submitting false credentials or some obscure IE method for deleting the cache. But I don't think these methods still work.

What still works ( I tested the following method with symfony 2.7 and google chrome 45 ) is replying to the client with a HTTP 401 unauthorized response.

Check it out:

Add the following to your logout section in the app/config/security.yml file

logout:
    success_handler: logout_listener

To your services configuration app/config/services.yml

logout_listener:
    class: AppBundle\LogoutListener

Then create a listener that responds with HTTP 401 unauthorized

<?php 

namespace AppBundle;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;

class LogoutListener implements  LogoutSuccessHandlerInterface 
{
    public function onLogoutSuccess(Request $request) 
    {
        return new Response('', 401);
    }
}

After logging out your app will send a 401 to the browser which will think authentication has failed resulting in the auth cache being cleared ( who wants to remember faulty credentials anyway right ) and prompt for your credentials again

Sign up to request clarification or add additional context in comments.

2 Comments

However, bouncing to http://[email protected]/your/page does work (i.e. trying to login with fake credentials in the URL). Not sure why this clears the cached authentication but a simple 401 response does not, but there you go.
Just tried the "logout@" trick on Chromium 56, it does not work
0

The answer of @Niki Van Cleemput doesn't seem to work in all cases. When I tested it, it was OK on Chrome 44 but not on Firefox 48. Here is a solution inspired by HTTP authentication logout via PHP:

The security parameters:

security:
    # ... your encoders, role_hierarchy, providers...
    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            pattern: ^/
            anonymous: ~
            stateless:  true
            http_basic:
                realm: "My admin area"
            # no logout parameter as it is handled manually

    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

The controller with the "fake" logout action:

<?php

namespace Me\Bundle\CoreBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
 * Security stuff.
 */
class SecurityController extends Controller
{
    /**
     * Logout confirmation.
     *
     * @Route("/logout", name="logout")
     */
    public function logoutAction()
    {
        return $this->render('@EasyAdmin/default/logout.html.twig'); // change with your template path
    }
}

In your layout:

<script type="text/javascript">
    function logout() {
        var xmlhttp;
        if (window.XMLHttpRequest) {
            xmlhttp = new XMLHttpRequest();
        }
        // code for IE
        else if (window.ActiveXObject) {
            xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
        }
        if (window.ActiveXObject) {
            // IE clear HTTP Authentication
            document.execCommand("ClearAuthenticationCache");
            window.location.href='{{ path('logout') }}';
        } else {
            xmlhttp.open("GET", '{{ path('easyadmin') }}', true, "logout", "logout");
            xmlhttp.send("");
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4) {window.location.href='{{ path('logout') }}';}
            }
        }

        return false;
    }
</script>

Add the logout link:

<a href="#" onclick="logout()"><i class="hidden-xs fa fa-user"></i> Logout</a>

At least it works in both Chrome and Firefox, let me know if it doesn't on other browsers.

3 Comments

This looks nasty... better and cleaner to bounce the user to http://[email protected]/your/page (where 'logout' is an invalid username). This should fail authentication and clear the related cache.
I agree, quite nasty. But for me It is the only solution that worked on every browser without having to call a "fake" user to logout.
imho, it is probably better/cleaner to call a fake user to force a logout rather than all this code (all you need is a logout link).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.