Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only Authenticating for certain urls in api key authentication #3815

Closed
javaguirre opened this issue Apr 23, 2014 · 13 comments
Closed

Only Authenticating for certain urls in api key authentication #3815

javaguirre opened this issue Apr 23, 2014 · 13 comments
Labels

Comments

@javaguirre
Copy link

I think this part is not valid, It couldn't work.

http://symfony.com/doc/current/cookbook/security/api_key_authentication.html#only-authenticating-for-certain-urls

If you just do this:

    public function createToken(Request $request, $providerKey)
    {
        // set the only URL where we should look for auth information
        // and only return the token if we're at that URL
        $targetUrl = '/login/check';
        if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
            return;
        }

        // ...
    }

The Token is null and then authenticate won't work because needs an instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface. The exact error is:

ContextErrorException: Catchable Fatal Error: 
Argument 1 passed to Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager::authenticate()
 must be an instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface, null given,

It happens here:

https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php#L77-L80

I am trying to sort things out. :-)

Thank you in advance!

@javaguirre
Copy link
Author

I solved it in security.yml creating an excluding regex for the pattern, using the example above:

security:
  ...

  firewalls:
    secured_area:
      pattern: ^/(?!login\/check)

I like the approach better also because this is a security matter, so not having to add the url inside the ApiKeyAuthentication class seems a good deal.

@weaverryan
Copy link
Member

Hi @javaguirre!

Your solution sounds good, but I'm not totally sure it should work - but obviously, you have it working, so I'm trying to understand :). If you authenticate at /login_check, that URL must be under your firewall, because when you set the token, you're setting it for whatever firewall you're in. But, that doesn't really matter :).

Because I think your original report may be valid. The createToken function is called here: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php#L119

And as you can see, the $token we return is passed directly to the authenticate method, which does require a Token object (null is not valid). So, when we return null in the example, I think this is a mistake. Instead, we need to return some token in all cases. Unfortunately, this causes the authenticateToken method to be called, where I think we need to return an authenticatedToken, or throw an exception (which would trigger them being asked to login).

So basically, I think you're right, but I'm not sure immediately what the proper solution is. Your solution doesn't make logical sense to me, but if you want to explain it further, it could be legitimate :).

Thanks!

@weaverryan weaverryan added the Bug label May 2, 2014
@javaguirre
Copy link
Author

I'll work on a solution, thank you for your answer. :-)

@petert82
Copy link

petert82 commented Jul 8, 2014

I ran up against this today, and kind of hacked my way around it by - in the case where I don't want to authenticate a particular URL - setting an empty 'api key' on the token returned by createToken. Then having authenticateToken throw an exception if the 'api key' is empty. Then having onAuthenticationFailure ignore any exceptions where the 'api key' is empty. This works for my use case, but wouldn't if, say an empty api key was really an error for your application.

It would certainly be nice to have a 'canonical' way of doing this, because as noted the current advice in the docs under 'Only Authenticating for Certain URLs' doesn't work!

@weaverryan
Copy link
Member

I believe a proposed fix for this is at symfony/symfony#11414.

@cirovargas
Copy link

Using this:

public function createToken(Request $request, $providerKey)
{
// set the only URL where we should look for auth information
// and only return the token if we're at that URL
$targetUrl = '/login/check';
if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
return;
}

    // ...
}

For me returns : "A Token was not found in the SecurityContext."

How i can solve this?

put on authenticateToken method:
return new PreAuthenticatedToken(
'anon.',
'',
$providerKey
);

????

@weaverryan
Copy link
Member

@cirovargas @peterrehm has a workaround here: symfony/symfony#11490 (comment)

@codymihai
Copy link

I have wrote this code http://symfony.com/doc/current/cookbook/security/api_key_authentication.html#cookbook-security-api-key-config

And now when I ran something like this: app/login/check?apiKey=mihai I receive:cNotFoundHttpException: No route found for "GET /app/login/check".

My config is:
*** Security **/
$app->register(
new Silex\Provider\SecurityServiceProvider(),
array(
'security.firewalls' => array(
/
'members-area' => array(
'pattern' => '^/app',
'anonymous' => true,
'form' => array(
'login_path' => '/app/login',
'check_path' => '/app/login/check',
'failure_path' => '/app/login',
'default_target_path' => '/app',
//'always_use_default_target_path' => true,
'username_parameter' => 'username',
'password_parameter' => 'password',
'use_referer' => true
),
'logout' => array(
'logout_path' => '/app/logout',
'target' => '/app',
'invalidate_session' => true,
),
'users' => $app['user.provider']
),*/
'secured_area' => array(
'pattern' => '^/app',
'stateless' => false,
'anonymous' => true,
'form' => array(
'authenticator' => $app['apiKey.authenticator']
),
'logout' => array(
'logout_path' => '/app/logout',
'target' => '/app',
'invalidate_session' => true,
),
'users' => $app['apiKey.provider'],

        ),
    ),
    'security.providers' => array(
        'api_key_user_provider'  => array(
            'id' => $app['apiKey.provider'],
        ),
    )
)

);

Could someone help me?
thanks

@weaverryan
Copy link
Member

Hi there?

Just create a route for /app/login/check. It doesn't need to do anything - the controller won't be called, but it has to exist.

Cheers!

@codymihai
Copy link

Hi @weaverryan ,
And should I do something in that route?
After I have created:
$app->match('/app/login/check', function() use ($app) {

});
The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?

@weaverryan
Copy link
Member

If you submit your form, this should result in a POST request to that URL, and then the security system will intercept it (so it won't even hit your controller). If you just surf to the URL, you'll get an error - but people will only submit the login form to get here (make sure you have method=POST on your login form)

@codymihai
Copy link

@weaverryan
This part is working with the form login.
But what I am trying to do is to login with http://localhost/myenms/web/app/login/check?apikey=mihai and I do not manage to do it. I have done this steps in my project: http://symfony.com/doc/current/cookbook/security/api_key_authentication.html#cookbook-security-api-key-config

And my config is:

$app->register(
new Silex\Provider\SecurityServiceProvider(),
array(
'security.firewalls' => array(
'members-area' => array(
'pattern' => '^/app',
'anonymous' => true,
'form' => array(
'login_path' => '/app/login',
'check_path' => '/app/login/check',
'failure_path' => '/app/login',
'default_target_path' => '/app',
//'always_use_default_target_path' => true,
'username_parameter' => 'username',
'password_parameter' => 'password',
'use_referer' => true
),
'logout' => array(
'logout_path' => '/app/logout',
'target' => '/app',
'invalidate_session' => true,
),
'users' => $app['user.provider']
),
'secured_area' => array(
'pattern' => '^/app',
'stateless' => false,
'anonymous' => true,
'form' => array(
'authenticator' => $app['apiKey.authenticator']
),
'logout' => array(
'logout_path' => '/app/logout',
'target' => '/app',
'invalidate_session' => true,
),
'users' => $app['apiKey.provider'],

        ),
    ),
    'security.providers' => array(
        'api_key_user_provider'  => array(
            'id' => $app['apiKey.provider'],
        ),
    )
)

);

@codymihai
Copy link

@weaverryan

In the final I have manage to do it.
I will post here how I did probably would help someone.

$app->match('/app/login/api/check', function() use ($app) {

$auth = new \Application\Security\ApiKeyAuthenticator($app['security.http_utils']);

$token = $auth->createToken($app['request'], 'mihai');
$result = $auth->authenticateToken($token, $app['apiKey.provider'], "mihai");

$app['security']->setToken($result);
return $app->redirect(
    $app['baseUrl'] . '/app'
);

});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants