lexeme
lexeme

Reputation: 2973

Responding with HTTP_UNAUTHORIZED/Redirect on session expiration

In my application I have a before() hook defined inside the routes.php file:

$app->before(function(Request $request) use($app) {

    $authAnon = $app['security']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

    if(!$request->getSession()->get('username') && $authAnon) {

        if($request->isXmlHttpRequest()) {
            // return 401/HTTP_UNAUTHORIZED response
            $response = new Response();
            $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
            $response->headers->set('Reason', 'SESSION_EXPIRED');
            $response->headers->set('WWW-Authenticate', 'MyAuthScheme realm="app:login"');
            return $response;
        }

        return new RedirectResponse('login', 301);

    }

}

But this results in $app['security'] not being found/defined:

InvalidArgumentException in Container.php line 96: Identifier "security" is not defined.

My security setup looks like this:

$app->register(new Silex\Provider\SecurityServiceProvider(), array(
    'security.firewalls' => array(
        'login' => array(
            'pattern' => '^/login$',
        ),
        'secured' => array(
            'pattern' => '^.*$',
            'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
            'logout' => array('logout_path' => '/logout', 'invalidate_session' => true),
            'users' => array(
                'admin' => array('ROLE_ADMIN', 'hashed_pwd'),
                'user' => array('ROLE_USER', 'hashed_pwd'),
            ),
        ),
    )
));

$app['security.role_hierarchy'] = array(
    'ROLE_ADMIN' => array('ROLE_USER'),
);

$app['security.access_rules'] = array(
    array('^/admin', 'ROLE_ADMIN'),
    array('^.*$', ['ROLE_USER']),
);

The order the session & security providers are registered looks as follows:

$config_path = __DIR__.'/../app/config/';
require_once $config_path.'session.php';
require_once $config_path.'security.php';
require_once $config_path.'routes/routes.php';

$app->run();

What am I doing wrong?

Edit

Please take a look at my answer below to see what I ended up with.

Upvotes: 2

Views: 752

Answers (3)

lexeme
lexeme

Reputation: 2973

Here's what I came up with in the end:

if($request->headers->has('XHR-Request')) {

    $isAuthFully = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY');

    if(!$isAuthFully and $request->get('_route') !== 'login') {

        $response = new Response();
        $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
        $response->headers->set('Login-Redirect', '');
        $response->headers->set('WWW-Authenticate', 'AppAuthScheme realm="application:login"');

        return $response;

    }
}

I added a custom XHR-Request header on the client side:

angular.module('documentsCatalogApp', []).config(httpConfig);

httpConfig.$inject = ['$httpProvider'];
function httpConfig($httpProvider) {
    $httpProvider.defaults.headers.common['XHR-Request'] = '';
}

Symfony's getRequest()->isXmlHttpRequest() relies on the X-Requested-With header which might be stripped out by the browser, at least I've seen a report on that about Firefox.

Then I added the interceptor:

// mount the config / interceptor to your Angular application
// .config(xhrLoginRedirectInterceptorConfig)
// .factory('xhrLoginRedirectInterceptor', xhrLoginRedirectInterceptor)

xhrLoginRedirectInterceptorConfig.$inject = ['$httpProvider'];
function xhrLoginRedirectInterceptorConfig($httpProvider) {
    $httpProvider.interceptors.push('xhrLoginRedirectInterceptor');
}

xhrLoginRedirectInterceptor.$inject = ['$q', '$window'];
function xhrLoginRedirectInterceptor($q, $window) {

    return {

        request: function(request) { return request; },
        response: function(response) { return response; },

        responseError: function(response) {

            var headersList = response.headers();
            var loginRequired = headersList && headersList.hasOwnProperty('Login-Redirect');

            if(response.status === 401 && loginRequired) {

                var url = "http://" + $window.location.host + baseUrl + "/login";
                $window.location.href = url;

            }

            return $q.reject(response);

        }

    }

}

References

  1. https://stackoverflow.com/a/22681873/532675
  2. https://github.com/angular/angular.js/issues/11008
  3. https://stackoverflow.com/a/30889331/532675

Upvotes: 0

Federkun
Federkun

Reputation: 36944

The security service has been deprecated and will be removed from Silex 2.0. If you want check the user's roles, then you need the security.authorization_checker service.

$authAnon = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

Since you're using the master (unstable) version you need to be careful about this things, or use a stable version instead.

Upvotes: 1

mTorres
mTorres

Reputation: 3590

My guess is that you're using a version of the Symfony Security Component >= 2.6+ right now, so according to the Silex docs you can't use the service security directly (as in $app['security']) and you have to use authorization checker service ($app['security.authorization_checker']):

<?php

$app->before(function(Request $request) use($app) {

  // This won't work with Symfony Security Component < 2.6
  // $authAnon = $app['security']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');

  // Under Symfony 2.6+
  $authAnon = $app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_ANONYMOUSLY');
  // ...
}

Upvotes: 1

Related Questions