Skip to content

A Token was not found in the SecurityContext (in cookbook/security/custom_authentication_provider.rst) #1033

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

Closed
c7nj7n opened this issue Jan 27, 2012 · 21 comments

Comments

@c7nj7n
Copy link

c7nj7n commented Jan 27, 2012

I followed the tutorial step by step, and everything seems to be alright.

But once I access the protected area (see my security settings below) I get a "A Token was not found in the SecurityContext". Any idea what's wrong? Maybe I miss something, or it's maybe the cookbook entry that is not explicit enough at one point?

firewalls:
    dev:
        pattern:        ^/(_(profiler|wdt)|css|images|js)/
        security:       false

    login:
        pattern:        ^/login$
        security:       false

    wsse_secured:
        pattern:        ^/test.json$
        wsse:           true
@stof
Copy link
Member

stof commented Jan 27, 2012

security: false means that you disable the firewall totally. So there is no token in the context on the login page. This means you cannot use isGranted in such cases.

@stof
Copy link
Member

stof commented Jan 27, 2012

and if you are not behind a firewall (for instance on every page different than /test.json in your case), it is exactly the same

@c7nj7n
Copy link
Author

c7nj7n commented Jan 27, 2012

Thanks for your quick answer. Sorry, but I fail to understand. Security is set to false for other parts of the website, I implemented the custom security context for a webservice only (the test.json for instance).

@stof
Copy link
Member

stof commented Jan 27, 2012

@cestcri the issue is probably that you tried to check some permissions (using isGranted for instance) in a paret where it is disabled.

@c7nj7n
Copy link
Author

c7nj7n commented Jan 27, 2012

Thanks for your help @stof

The thing is that I didn't try anything, I just followed the tutorial step by step and tried to access text.json - that's all. Unfortunately the tutorial doesn't cover the usage... or at least its unclear to me.

@c7nj7n
Copy link
Author

c7nj7n commented Jan 27, 2012

I thought this issue was linked to http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html and thus https://github.com/symfony/symfony-docs/commits/master/cookbook/security/custom_authentication_provider.rst, but apparently this is not the case. Sorry for not giving the context right away...

@bshaffer
Copy link
Contributor

Check out this change:

https://github.com/bshaffer/symfony-docs/commit/fdff03e0f6f527fdc37c9a20bd9f54331c3412de

There was a return statement that fails to set a 403 response before doing so. Pull request pending, because there are a few other issues with the article, but this will get you past the Token error.

The other issue is that the "security_factories" directive has been removed,
so factories must be added in the AcmeDemoBundle.php class like this:

class AcmeDemoBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new WsseFactory());
    }
}

Although I am still looking into alternatives...

@c7nj7n
Copy link
Author

c7nj7n commented Jan 31, 2012

Thanks for working on this, Brent.

Thanks for the fix, Brent. I applied the changes to MyProject\MyBundle\Security\Firewall\WsseListener and the message disappears.

Unfortunately I get now the following, although the function you asked me to add is identical to the interface definition in BundleInterface:

Fatal error: Declaration of MyProject\MyBundle\MyProjectMyBundle::build() must be compatible with that of Symfony\Component\HttpKernel\Bundle\BundleInterface::build() in C:\wamp\www\poc\src\MyProject\MyBundle\MyProjectMyBundle.php on line 16

@bshaffer
Copy link
Contributor

Make sure you use the proper namespaces at the top of your Bundle file:

    namespace MyProject\MyBundle;

    use MyProject\MyBundle\DependencyInjection\Security\Factory\WsseFactory;
    use Symfony\Component\HttpKernel\Bundle\Bundle;
    use Symfony\Component\DependencyInjection\ContainerBuilder;

@c7nj7n
Copy link
Author

c7nj7n commented Jan 31, 2012

Good point, I didn't include the ContainerBuilder!

Now I got:

Fatal error: Call to undefined method Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::addSecurityListenerFactory() inC:\wamp\www\poc\src\MyProject\MyBundle\MyProjectMyBundle.php on line 16

Sorry about that...

@stof
Copy link
Member

stof commented Jan 31, 2012

Seems like you are trying to use the new way to register the factories without using the new code. This method is new in Symfony 2.1

@bshaffer
Copy link
Contributor

Thanks stof. Looks like < 2.1 can use the old way of doing it:

  # app/config/security.yml
    security:
        factories:
            - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.yml"

So just change your WsseListener file and you'll be all set.

@c7nj7n
Copy link
Author

c7nj7n commented Feb 1, 2012

Thanks stof and Brent for you help.

I updated the WsseListener already following https://github.com/bshaffer/symfony-docs/commit/fdff03e0f6f527fdc37c9a20bd9f54331c3412de - is there anything else to do?

The settings in security.yml are identical with those in the initial cookbook article, so no changes here. Do I need to keep the addSecurityListenerFactory(new WsseFactory() in my bundle's main class?

For now I have either a blank page (without the changes to my bundle's main class or the error described above (Call to undefined method Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::addSecurityListenerFactory()). Sorry, but I'm still stuck.

@bshaffer
Copy link
Contributor

bshaffer commented Feb 1, 2012

Remove the whole build() function declaration in AcmeDemoBundle (including the call to addSecurityListenerFactory). This is only if you are on symfony 2.1. The WsseListener class should be the only one you change, and this will fix the "Token not found in SecurityContext" error.

@c7nj7n
Copy link
Author

c7nj7n commented Feb 1, 2012

That's how I understood this, done. This brings me back to the blank page. Maybe I'm missing something, actually I don't even know how to pass the token neither where to set this.

Sorry about these premature questions, all I wanted to do is securing a webservice. I stumbled upon your great article and put a custom authentication provider in place. Now I guess I need to correctly use it, I just don't know how. Maybe you could add a section "How to use" to the article?

@bshaffer
Copy link
Contributor

bshaffer commented Feb 1, 2012

Is the blank page a 403? If so, you're good to go.

@c7nj7n
Copy link
Author

c7nj7n commented Feb 1, 2012

Yes, it is a forbidden (should have had the idea to check for this on my own, sorry about that). How to grant access via a token now?

@bshaffer
Copy link
Contributor

bshaffer commented Feb 1, 2012

Well, you have to make the call via WSSE. WSSE was used purely for academic reasons. You can substitute it with your own security provider. A call via WSSE requires setting certain WSSE properties in your header and encrypting them accordingly. So it would look something like this:

#!/usr/local/bin/php
<?php 
// path/to/wsse.php
echo generate_wsse_header('YOUR-USERNAME', 'YOUR-API-KEY');

  function generate_wsse_header($username, $secret)
  {
    date_default_timezone_set('America/Denver');
    $nonce = md5(rand(), true);
    $created = gmdate('Y-m-dTH:i:sZ');

    $digest = base64_encode(sha1($nonce.$created.$secret,true));
    $b64nonce = base64_encode($nonce);

    return sprintf('X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
      $username,
      $digest,
      $b64nonce,
      $created
    );
  }

Which you could then test via a curl request like this:

$ curl --get --header "$(/path/to/wsse.php)" https://myhost/test.json

If this returns true, you're good to go. WSSE is meant primarily for API calls, as there is no maintaining of authenticated state.

@c7nj7n
Copy link
Author

c7nj7n commented Feb 2, 2012

Great, thanks for walking me through. I'm looking for the API authentication. So that's good. I tried now the following, and get a 403 (normal):

<?php

function generate_wsse_header($username, $secret)
{
    date_default_timezone_set('America/Denver');
    $nonce = md5(rand(), true);
    $created = gmdate('Y-m-dTH:i:sZ');

    $digest = base64_encode(sha1($nonce.$created.$secret,true));
    $b64nonce = base64_encode($nonce);

    return sprintf('X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
        $username,
        $digest,
        $b64nonce,
        $created
    );
}

$curl_handle = curl_init();

curl_setopt($curl_handle, CURLOPT_URL, 'http://localhost/app_dev.php/test.json');
curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array(generate_wsse_header('APIUSER', 'APIKEY')));

curl_exec($curl_handle);
echo curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); 

curl_close($curl_handle);

My last question: where to set the token on the application side?

I guess I need to do something here:

class WsseUserToken extends AbstractToken
{
    public $created;
    public $digest;
    public $nonce;

    public function getCredentials()
    {
        return '';
    }
}

@bshaffer
Copy link
Contributor

bshaffer commented Feb 2, 2012

You need to have an actual username and password instead of API_USER and API_KEY. This is done with whatever user provider you have (in memory, using FOSUserBundle, etc).

The user providers are agnostic to the security providers. This is what makes the Security component so great! Check out this article for more info.

@c7nj7n
Copy link
Author

c7nj7n commented Feb 3, 2012

This works like a charm, you are a champ! Thanks a lot for your help Brent!

Two changes I had to made though:

  1. I had to change the date creation in generate_wsse_header to get through the strtotime($created) in validateDigest:
//$created = gmdate('Y-m-dTH:i:sZ');
$created = gmdate('Y-m-d\TH:i:s\Z');
  1. In validateDigest I had to suppress warnings/errors when writing the cache file:
//file_put_contents($this->cacheDir.'/'.$nonce, time());
@file_put_contents($this->cacheDir.'/'.$nonce, time());

Now it works like a charm, thanks a million for your help!

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

No branches or pull requests

3 participants