Sunday, April 8, 2012

Securing Symfony2 REST services with FOSOAuthServerBundle

Overview

In my previous article, I wrote about setting up a Symfony2 REST service using FOSRestBundle. However, this REST service was behind a firewall protected by a generic form_login provider. Not really ideal if you wish to open your REST API to other applications. So in this article, I will try to explain how to set up FOSOAuthServerBundle to protect your REST API methods using OAuth2. Before we start getting into the gritty details, it is a good idea to have a look at the official OAuth2 documentation. Let's begin...

FOSOAuthServerBundle Installation

You have to install v1.1.0 of FOSOAuthServerBundle if you are using Symfony 2.0.x. If not, see the docs.

First, add the following entries to your deps file:

[FOSOAuthServerBundle]
    git=git://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git
    target=bundles/FOS/OAuthServerBundle
    version=origin/1.1.x
[oauth2-php]
    git=git://github.com/FriendsOfSymfony/oauth2-php.git

Run the vendors script to install these dependencies:

php bin/vendors install

Update your app/autoload.php file:

$loader->registerNamespaces(array(
    
    'FOS'    => __DIR__.'/../vendor/bundles',
    'OAuth2' => __DIR__.'/../vendor/oauth2-php/lib',

));

Register the FOSOAuthServerBundle in your app/AppKernel.php file:

public function registerBundles()
{
    $bundles = array(
        
        new FOS\OAuthServerBundle\FOSOAuthServerBundle(),

    );
}

Model Setup

Because I will be using MongoDB as the backend, we have to setup our document classes. First, create a new bundle called AcmeOAuthServerBundle by running "php app/console generate:bundle". Next, create the following model files in the Document directory.

In regards to the client clasas, I decided to add a "name" property in order to display an identity to a user on the authorization page. All other document files contain the bare minimum properties required by the FOSOAuthServerBundle as illustrated below.

src/Acme/OAuthServerBundle/Document/Client.php

namespace Acme\OAuthServerBundle\Document;

use FOS\OAuthServerBundle\Document\Client as BaseClient;

class Client extends BaseClient 
{
    protected $id;
    protected $name;
    
    public function __construct()
    {
        parent::__construct();
    }
    
    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }    

}

src/Acme/OAuthServerBundle/Resources/config/doctrine/Client.mongodb.xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
                    http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">

    <document name="Acme\OAuthServerBundle\Document\Client" db="acme" collection="oauthClient" customId="true">
        
        <field fieldName="id" id="true" strategy="AUTO" />
        <field fieldName="name" type="string" />
        
    </document>
    
</doctrine-mongo-mapping>

src/Acme/OAuthServerBundle/Document/AccessToken.php

namespace Acme\OAuthServerBundle\Document;

use FOS\OAuthServerBundle\Document\AccessToken as BaseAccessToken;
use FOS\OAuthServerBundle\Model\ClientInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class AccessToken extends BaseAccessToken 
{
    protected $id;
    protected $client;
    protected $user;
    
    public function getClient()
    {
        return $this->client;
    }

    public function setClient(ClientInterface $client)
    {
        $this->client = $client;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function setUser(UserInterface $user)
    {
        $this->user = $user;
    }
    
}

src/Acme/OAuthServerBundle/Resources/config/doctrine/AccessToken.mongodb.xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
                    http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">

    <document name="Acme\OAuthServerBundle\Document\AccessToken" db="acme" collection="oauthAccessToken" customId="true">
        
        <field fieldName="id" id="true" strategy="AUTO" />
        
        <reference-one target-document="Acme\OAuthServerBundle\Document\Client" field="client" />
        <reference-one target-document="Acme\UserBundle\Document\User" field="user" />
        
    </document>
    
</doctrine-mongo-mapping>

src/Acme/OAuthServerBundle/Document/RefreshToken.php

namespace Acme\OAuthServerBundle\Document;

use FOS\OAuthServerBundle\Document\RefreshToken as BaseRefreshToken;
use FOS\OAuthServerBundle\Model\ClientInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class RefreshToken extends BaseRefreshToken 
{
    protected $id;
    protected $client;
    protected $user;
    
    public function getClient()
    {
        return $this->client;
    }

    public function setClient(ClientInterface $client)
    {
        $this->client = $client;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function setUser(UserInterface $user)
    {
        $this->user = $user;
    }
    
}

src/Acme/OAuthServerBundle/Resources/config/doctrine/RefreshToken.mongodb.xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
                    http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">

    <document name="Acme\OAuthServerBundle\Document\RefreshToken" db="acme" collection="oauthRefreshToken" customId="true">
        
        <field fieldName="id" id="true" strategy="AUTO" />
        
        <reference-one target-document="Acme\OAuthServerBundle\Document\Client" field="client" />
        <reference-one target-document="Acme\UserBundle\Document\User" field="user" />
        
    </document>
    
</doctrine-mongo-mapping>

src/Acme/OAuthServerBundle/Document/AuthCode.php

namespace Acme\OAuthServerBundle\Document;

use FOS\OAuthServerBundle\Document\AuthCode as BaseAuthCode;
use FOS\OAuthServerBundle\Model\ClientInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class AuthCode extends BaseAuthCode 
{
    protected $id;
    protected $client;
    protected $user;
    
    public function getClient()
    {
        return $this->client;
    }

    public function setClient(ClientInterface $client)
    {
        $this->client = $client;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function setUser(UserInterface $user)
    {
        $this->user = $user;
    }
    
}

src/Acme/OAuthServerBundle/Resources/config/doctrine/AuthCode.mongodb.xml

<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
                    http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">

    <document name="Acme\OAuthServerBundle\Document\AuthCode" db="acme" collection="oauthAuthCode" customId="true">
        
        <field fieldName="id" id="true" strategy="AUTO" />
        
        <reference-one target-document="Acme\OAuthServerBundle\Document\Client" field="client" />
        <reference-one target-document="Acme\UserBundle\Document\User" field="user" />
        
    </document>
    
</doctrine-mongo-mapping>

Update your app/config/config.yml to register your MongoDB model files:

fos_oauth_server:
    db_driver:  mongodb
    client_class:        Acme\OAuthServerBundle\Document\Client
    access_token_class:  Acme\OAuthServerBundle\Document\AccessToken
    refresh_token_class: Acme\OAuthServerBundle\Document\RefreshToken
    auth_code_class:     Acme\OAuthServerBundle\Document\AuthCode

Security Configuration

Once the model setup is complete, we are going to setup the security.yml file. We are going to enforce authentication before accessing the /oauth/v2/auth route. This will be accomplished by adding a login form before displaying the authorization page. In addition, since I am already using FOSUserBundle, I will set the security.firewalls.oauth_authorize.form_login.provider configuration parameter to fos_userbundle. This means that once the login form is filled out and posted, the user will then be redirected to the FOSOAuthServerBundle authorization end point.

First, add the following to the app/config/security.yml file:

security:
    firewalls:
        api:
            pattern: ^/api
            fos_oauth: true
            stateless: true
        oauth_authorize:
            pattern: ^/oauth/v2/auth 
            form_login:
                provider: fos_userbundle
                check_path: /oauth/v2/auth_login_check
                login_path: /oauth/v2/auth_login
            anonymous: true
        oauth_token:
            pattern: ^/oauth/v2/token
            security: false      

    access_control:
        - { path: ^/oauth/v2/auth_login$, role: IS_AUTHENTICATED_ANONYMOUSLY }

If you are running Symfony version 2.0.X add the following to your app/config/config.yml file:

imports:
    - { resource: "@FOSOAuthServerBundle/Resources/config/security.yml" }

Here is what the above configuration means. We are going to protect our authorization endpoint (/oauth/v2/auth) with the Symfony2's built in form_login authentication provider. Basically, any unauthenticated user will be forwarded to /oauth/v2/oauth_login page. Once the form is submitted and the user is successfully authenticated, he/she will be forwarded to the the /oauth/v2/auth endpoint where another form asking for an authorization grant to the respective client be displayed. Once the user grants access, he/she will be redirected to the redirect_uri as specified by the OAuth2 documentation.

The redirection to the /oauth/v2/auth will take place using the built in referrer redirect functionality of the Symfony security component. This is an important assumption as the OAuth2 flow will not work if the OAuth2 client makes a request to any URL other than the /oauth/v2/auth endpoint. The previous sentence will become much clearer when you read the "testing" section at the end of this article. You may want to have a look at the Security Configuration Reference for more information about the successful authentication redirection flow and its configuration.

First, we need to create our login and authorization forms. This part is pretty straight forward and outside the scope of this article, so I will skip the lengthy explanations.

src/Acme/OAuthServerBundle/Form/Type/AuthorizeFormType.php

namespace Acme\OAuthServerBundle\Form\Type;

use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\AbstractType;

class AuthorizeFormType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('allowAccess', 'checkbox', array(
            'label' => 'Allow access',
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Acme\OAuthServerBundle\Form\Model\Authorize');
    }

    public function getName()
    {
        return 'acme_oauth_server_authorize';
    }
    
}

src/Acme/OAuthServerBundle/Form/Model/Authorize.php

namespace Acme\OAuthServerBundle\Form\Model;

class Authorize
{
    protected $allowAccess;
    
    public function getAllowAccess()
    {
        return $this->allowAccess;
    }

    public function setAllowAccess($allowAccess)
    {
        $this->allowAccess = $allowAccess;
    }
}

src/Acme/OAuthServerBundle/Form/Handler/AuthorizeFormHandler.php

namespace Acme\OAuthServerBundle\Form\Handler;

use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Acme\OAuthServerBundle\Form\Model\Authorize;
use Symfony\Component\Security\Core\SecurityContextInterface;
use OAuth2\OAuth2;
use OAuth2\OAuth2ServerException;
use OAuth2\OAuth2RedirectException;

class AuthorizeFormHandler
{
    protected $request;
    protected $form;
    protected $context;
    protected $oauth2;

    public function __construct(Form $form, Request $request, SecurityContextInterface $context, OAuth2 $oauth2)
    {
        $this->form = $form;
        $this->request = $request;
        $this->context = $context;
        $this->oauth2 = $oauth2;
    }

    public function process(Authorize $authorize)
    {
        $this->form->setData($authorize);

        if ($this->request->getMethod() == 'POST') {
            
            $this->form->bindRequest($this->request);

            if ($this->form->isValid()) {
                
                try {
                    $user = $this->context->getToken()->getUser();
                    return $this->oauth2->finishClientAuthorization(true, $user, $this->request, null);
                } catch (OAuth2ServerException $e) {
                    return $e->getHttpResponse();
                }
                
            }
            
        }

        return false;
    }

}

src/Acme/OAuthServerBundle/Resources/config/services.xml

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        
        <service id="acme_oauth_server.authorize.form_type" class="Acme\OAuthServerBundle\Form\Type\AuthorizeFormType">
        </service>
        
        <service id="acme_oauth_server.authorize.form" factory-method="createNamed" factory-service="form.factory" class="Symfony\Component\Form\Form">
            <argument type="service" id="acme_oauth_server.authorize.form_type" />
            <argument>acme_oauth_server_auth</argument>
        </service>
        
        <service id="acme_oauth_server.authorize.form_handler" class="Acme\OAuthServerBundle\Form\Handler\AuthorizeFormHandler" scope="request">
            <argument type="service" id="acme_oauth_server.authorize.form" />
            <argument type="service" id="request" />
            <argument type="service" id="security.context" />
            <argument type="service" id="fos_oauth_server.server" />
        </service>
        
     </services>
     
</container>

src/Acme/OAuthServerBundle/Resources/config/validation.xml

<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
        http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">

    <class name="Acme\OAuthServerBundle\Form\Model\Authorize">

        <property name="allowAccess">
            <constraint name="True">
                <option name="message">Please check the checkbox to allow access to your profile.</option>
                <option name="groups">
                    <value>Authorize</value>
                </option>
            </constraint>
        </property>
        
    </class>
    
</constraint-mapping>

Once the form setup is complete, override the AuthorizeController class. In order to do this, we need to ensure that our bundle is a child of FOSOauthServerBundle by adding the the getParent method call to your src/Acme/OAuthServerBundle/AcmeOAuthServerBundle.php file:

namespace Acme\OAuthServerBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeOAuthServerBundle extends Bundle
{
    public function getParent()
    {
        return 'FOSOAuthServerBundle';
    }
}

Create the new AuthorizeController at src/Acme/OAuthServerBundle/Controller/AuthorizeController.php

namespace Acme\OAuthServerBundle\Controller;

use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\Request;
use FOS\OAuthServerBundle\Controller\AuthorizeController as BaseAuthorizeController;
use Acme\OAuthServerBundle\Form\Model\Authorize;
use Acme\OAuthServerBundle\Document\Client;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class AuthorizeController extends BaseAuthorizeController
{
    public function authorizeAction(Request $request)
    {
        if (!$request->get('client_id')) {
            throw new NotFoundHttpException("Client id parameter {$request->get('client_id')} is missing.");
        }
        
        $clientManager = $this->container->get('fos_oauth_server.client_manager.default');
        $client = $clientManager->findClientByPublicId($request->get('client_id'));
        
        if (!($client instanceof Client)) {
            throw new NotFoundHttpException("Client {$request->get('client_id')} is not found.");
        }
        
        $user = $this->container->get('security.context')->getToken()->getUser();
        
        $form = $this->container->get('acme_oauth_server.authorize.form');
        $formHandler = $this->container->get('acme_oauth_server.authorize.form_handler');
        
        $authorize = new Authorize();
        
        if (($response = $formHandler->process($authorize)) !== false) {
            return $response;
        }
                
        return $this->container->get('templating')->renderResponse('AcmeOAuthServerBundle:Authorize:authorize.html.php', array(
            'form' => $form->createView(),
            'client' => $client,
        ));
    }
}

Next, add a new controller named SecurityController located at src/Acme/OAuthServerBundle/Controller/SecurityController.php. This controller will handle the form_login authorization for us.

namespace Acme\OAuthServerBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;

class SecurityController extends Controller
{
    public function loginAction(Request $request)
    {
        $session = $request->getSession();
        
        if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
        } elseif (null !== $session && $session->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
            $session->remove(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = '';
        }

        if ($error) {
            $error = $error->getMessage(); // WARNING! Symfony source code identifies this line as a potential security threat.
        }
        
        $lastUsername = (null === $session) ? '' : $session->get(SecurityContext::LAST_USERNAME);

        return $this->render('AcmeOAuthServerBundle:Security:login.html.php', array(
            'last_username' => $lastUsername,
            'error'         => $error,
        ));
    }
    
    public function loginCheckAction(Request $request)
    {
        
    }
}

Define your routes for these actions in your src/Acme/OAuthServerBundle/Resources/config/routing/security.xml

<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="acme_oauth_server_auth_login" pattern="/oauth/v2/auth_login">
        <default key="_controller">AcmeOAuthServerBundle:Security:login</default>
    </route>
    
    <route id="acme_oauth_server_auth_login_check" pattern="/oauth/v2/auth_login_check">
        <default key="_controller">AcmeOAuthServerBundle:Security:loginCheck</default>
    </route>
    
</routes>

Next, update your app/config/routing.yml file:

fos_oauth_server_token:
    resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml"

fos_oauth_server_authorize:
    resource: "@FOSOAuthServerBundle/Resources/config/routing/authorize.xml"
    
acme_oauth_server_security:
    resource: "@AcmeOAuthServerBundle/Resources/config/routing/security.xml"
    prefix: /

Next, we need to setup our views. We need one for the login form with _username and _password fields that are processed by the security component's authorization listener.

src/Acme/OAuthServerBundle/Resources/views/Security/login.html.php

<?php $view['slots']->start('body') ?>
    <div class="form">
        <form id="login" class="vertical" action="<?php echo $view['router']->generate('acme_oauth_server_auth_login_check') ?>" method="post">
            <div class="form_title">
                OAuth Authorization
            </div>
            <?php if ($error): ?>
                <div class='form_error'><?php echo $view->escape($error); ?></div>
            <?php endif; ?>
            <div class="form_item">
                <div class="form_label"><label for="username">Username</label>:</div>
                <div class="form_widget"><input type="text" id="username" name="_username" /></div>
            </div>
            <div class="form_item">
                <div class="form_label"><label for="password">Password</label>:</div>
                <div class="form_widget"><input type="password" id="password" name="_password" /></div>
            </div>
            <div class="form_button">
                <input type="submit" id="_submit" name="_submit" value="Log In" />
            </div>
        </form>
    </div>
<?php $view['slots']->stop() ?>

Here is the other form that allows the user to grant access to a OAuth2 client:

src/Acme/OAuthServerBundle/Resources/views/Authorize/authorize.html.php

<?php $view['slots']->start('body') ?>
    <div class="form">
        <form class="vertical" action="<?php echo $view['router']->generate('fos_oauth_server_authorize', array(
            'client_id' => $view['request']->getParameter('client_id'),
            'response_type' => $view['request']->getParameter('response_type'),
            'redirect_uri' => $view['request']->getParameter('redirect_uri'),
            'state' => $view['request']->getParameter('state'),
            'scope' => $view['request']->getParameter('scope'),
        )) ?>" method="POST"  <?php echo $view['form']->enctype($form); ?>>
            <div class="form_title">
                Grant access to <?php echo $view->escape($client->getName()); ?>?
            </div>
            <?php echo $view['form']->widget($form) ?>
            <div class="form_button">
                <input type="submit" value="Authorize" />
            </div>
        </form>
    </div>
<?php $view['slots']->stop() ?>

Testing

Create a new client

First, lets add a command line utility to our bundle to create a new OAuth2 client in MongoDB:

src/Acme/OAuthServerBundle/Command/ClientCreateCommand.php

namespace Acme\OAuthServerBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Acme\OAuthServerBundle\Document\Client;

class ClientCreateCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('acme:oauth-server:client:create')
            ->setDescription('Creates a new client')
            ->addArgument('name', InputArgument::REQUIRED, 'Sets the client name', null)
            ->addOption('redirect-uri', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.', null)
            ->addOption('grant-type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Sets allowed grant type for client. Use this option multiple times to set multiple grant types..', null)
            ->setHelp(<<<EOT
The <info>%command.name%</info>command creates a new client.

  <info>php %command.full_name% [--redirect-uri=...] [--grant-type=...] name</info>
   
EOT
        );
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $clientManager = $this->getContainer()->get('fos_oauth_server.client_manager.default');
        $client = $clientManager->createClient();
        $client->setName($input->getArgument('name'));
        $client->setRedirectUris($input->getOption('redirect-uri'));
        $client->setAllowedGrantTypes($input->getOption('grant-type'));
        $clientManager->updateClient($client);
        $output->writeln(sprintf('Added a new client with name <info>%s</info> and public id <info>%s</info>.', $client->getName(), $client->getPublicId()));        
    }
}

Now, run the following command to create a new client.

[burak@localhost platform]$ php app/console acme:oauth-server:client:create --redirect-uri=http://www.google.com --grant-type=token --grant-type=authorization_code ClientName
Added a new client with name ClientName and public id 4f8e5bb57f8b9a0816000000_1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s.

As you can see below, we have created a new client entry in our oauthClient MongoDB collection:

{
   "_id": ObjectId("4f8e5bb57f8b9a0816000000"),
   "randomId": "1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s",
   "redirectUris": {
     "0": "http:\/\/www.google.com"
  },
   "secret": "147v1qcgxvuscg4owg4480ww484kc0ow0cwgkw0c4g4g8oowkc",
   "allowedGrantTypes": {
     "0": "token",
     "1": "authorization_code"
  },
   "name": "ClientName"
}

Now we can use this client's public id to make some test requests. OAuth2 supports four types of authorization grant flows:

The Authorization Code Grant flow generates an authorization code when the user grants access and the OAuth2 client needs to make a subsequent request to get the access and refresh tokens.

In the Implicit Grant flow, an access token is immediately generated and sent back to the OAuth2 client. Refresh tokens are not supported in this flow.

In the Resource Owner Password Credentials Grant flow, the OAuth2 client sends the user's login name and password to the OAuth2 authorization server to get the access and refresh tokens. This implies a trust relationship between the user and the OAuth2 client.

Finally, in the Client Credentials Grant flow, the OAuth2 client authenticates with the server and requests an access token directly. According to the documentation, a refresh token should not be included in this flow.

In our case, we will be testing the Authorization Code Grant and Implicit Grant flows.

Authorization Code Grant Flow

Open your browser and enter the following into your address bar:

http://acme.localhost/app_dev.php/oauth/v2/auth?client_id=4f8e5bb57f8b9a0816000000_1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s&response_type=code&redirect_uri=http%3A%2F%2Fwww.google.com

After you login and grant access, you should be redirected to:

http://www.google.com/?code=6c7136745d8556650cb5e0d5cd53029c925aae72

In fact, your MongoDB oauthAuthCode collection should also have a new record:

{
   "_id": ObjectId("4f8e64b97f8b9a8d05000000"),
   "token": "6c7136745d8556650cb5e0d5cd53029c925aae72",
   "redirectUri": "http: \/\/www.google.com",
   "expiresAt": 1334731991,
   "scope": null,
   "client": {
     "$ref": "oauthClient",
     "$id": ObjectId("4f8e5bb57f8b9a0816000000"),
     "$db": "acme"
   },
   "user": {
     "$ref": "user",
     "$id": ObjectId("4f7f79ac7f8b9a000f000001"),
     "$db": "acme"
   } 
}

Now, call the /oauth/v2/token endpoint to get your access and refresht tokens. Enter the following URL in your browser's address bar:

http://acme.localhost/app_dev.php/oauth/v2/token?client_id=4f8e5bb57f8b9a0816000000_1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s&client_secret=147v1qcgxvuscg4owg4480ww484kc0ow0cwgkw0c4g4g8oowkc&grant_type=authorization_code&redirect_uri=http%3A%2F%2Fwww.google.com&code=6c7136745d8556650cb5e0d5cd53029c925aae72
{
    "access_token": "8315796acc79f6a1bfb4e4935aea01362d59ecce",
    "expires_in": 3600,
    "token_type": "bearer",
    "scope": null,
    "refresh_token": "da359ceafe501fd2445df0a6c406953264e54c47"
}

Access token stored in MongoDB

{
   "_id": ObjectId("4f8e67d87f8b9a8e05000001"),
   "token": "8315796acc79f6a1bfb4e4935aea01362d59ecce",
   "expiresAt": 1334736360,
   "scope": null,
   "client": {
     "$ref": "oauthClient",
     "$id": ObjectId("4f8e5bb57f8b9a0816000000"),
     "$db": "acme"
   },
   "user": {
     "$ref": "user",
     "$id": ObjectId("4f7f79ac7f8b9a000f000001"),
     "$db": "acme"
   }
}

Refresh token stored in MongoDB

{
   "_id": ObjectId("4f8e67d87f8b9a8e05000002"),
   "token": "da359ceafe501fd2445df0a6c406953264e54c47",
   "expiresAt": 1335942360,
   "scope": null,
   "client": {
     "$ref": "oauthClient",
     "$id": ObjectId("4f8e5bb57f8b9a0816000000"),
     "$db": "acme"
   },
   "user": {
     "$ref": "user",
     "$id": ObjectId("4f7f79ac7f8b9a000f000001"),
     "$db": "acme"
   }
}

Implicit Grant Flow

I was able to test this flow only after manually editing the FOS/OAuthServerBundle/Storage/OAuthStorage.php file and implementing the IOAuth2GrantImplicit interface.

use OAuth2\IOAuth2GrantImplicit;

class OAuthStorage implements IOAuth2RefreshTokens, IOAuth2GrantUser, IOAuth2GrantCode, IOAuth2GrantImplicit
{
...
}

As of the date of this article, the implicit grant flow does not work without these change. Nevertheless, I have decided to include section just for reference.

UPDATE 2012/05/28: This issue is fixed in the master branch.

Anyway, to test the implicit grant flow, open your browser and enter the following into your address bar:

http://acme.localhost/app_dev.php/oauth/v2/auth?client_id=4f8e5bb57f8b9a0816000000_1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s&redirect_uri=http%3A%2F%2Fwww.google.com&response_type=token

After you login and authorize the request, you should be redirected with your access and refresh tokens right away:

http://www.google.com/#access_token=1a4b82f02bdb7425d14948d686f76e777e5d0a65&expires_in=3600&token_type=bearer&refresh_token=43937901d873da5e80a8f59c632b6eab59988c54

Obviously, this is not really proper testing. It would be much better if an actual application makes these requests. So in one of my future articles, I will try to write a tutorial about an Android client making requests to a Symfony2 REST API protected by FOSOAuthServerBundle. For now, that's it. Phew...

UPDATE 2012/06/04: Modified the article to reflect some changes introduced in branch 1.1.x.

87 comments:

  1. Thanks for the article! I look forward to continuing ...

    I would like to learn more (see more code) of the FOS User + REST API + Auth.

    ReplyDelete
  2. This tutorial has been fantastic. It's definitely help me wade into the OAuth waters.

    I'm stuck in one place and it's not really covered in any detail here.

    Right now my login page (oauth/v2/auth_login) works but it's not passing a client_id to my auth form (oauth/v2/auth), causing errors. This seems logical to me, as the logic in the AuthorizeController.php checks for a client_id and throws an exception. If I attack the auth form using the Authorization Code Grant Flow method, the results are as expected (not that I fully understand them yet). So, as of right now I'm stuck. My gut tells me that there's something supposed to happen post-login that gathers the needed parameters (e.g. client_id) and hands me off to the auth form without error.

    Please advise.

    Thank you.
    -andrew

    ReplyDelete
    Replies
    1. Did you get anywhere with the missing client id? I have the same thing.

      Delete
    2. Did you setup your referrer redirect? Make sure "use_referrer" is set for your form_login. See http://symfony.com/doc/current/cookbook/security/form_login.html for more information.

      Delete
    3. had the same problem, turned out that the if statement check if instanceof Client, and it was using the wrong client namespace, Acme\OAuthServerBundle\Document\Client but once I matched my own Client entity namespace it worked ok ;)

      Delete
  3. Thanks Burak, Thanks for the tutorial
    I am stuck with one problem, Any help will be appreciated.

    Right now oauth/v2/auth takes me to the /login and once i put the password it takes me to authorize.html.php

    in chrome though if i do not check the check-box element it shows me a message "please check the box to proceed" whereas in IE it doesnt. If i check on the allow access, I am getting the following error.
    It seems to be trying to loading Symfony\Component\Form/Resources/config/validation.xml instead of the
    src/Acme/OAuthServerBundle/Resources/config/validation.xml which i have created according to your example.

    I am using symfony 2.0.13.


    Warning: DOMDocument::schemaValidate(): Invalid Schema in D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Validator\Mapping\Loader\XmlFileLoader.php

    at ErrorHandler ->handle ('2', 'DOMDocument::schemaValidate(): Invalid Schema', 'D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Validator\Mapping\Loader\XmlFileLoader.php', '182', array('file' => 'D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Form/Resources/config/validation.xml', 'dom' => object(DOMDocument)))
    3. at DOMDocument ->schemaValidate ('D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Validator\Mapping\Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd')
    in D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Validator\Mapping\Loader\XmlFileLoader.php at line 182

    4. at XmlFileLoader ->parseFile ('D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Form/Resources/config/validation.xml')
    in D:\Apache2.2\htdocs\milestone_1\vendor\symfony\src\Symfony\Component\Validator\Mapping\Loader\XmlFileLoader.php at line 32

    ReplyDelete
  4. Thank you very much for this tutorial!! It's very interesting!

    But I'm blocked in a problems... any help will be very nice, I've been two days looking for more info... but I can't found it.

    I receive the next error:

    Fatal error: Declaration of RedRudeBoy\OAuthServerBundle\Form\Type\AuthorizeFormType::buildForm() must be compatible with that of Symfony\Component\Form\FormTypeInterface::buildForm() in ...src/RedRudeBoy/OAuthServerBundle/Form/Type/AuthorizeFormType.php on line 26

    Thanks!!

    ReplyDelete
    Replies
    1. Arnau-Lenin: In AuthorizeFormType.php you need to change buildForm to accept FormBuilderInterface rather than FormBuilder. Also update your 'use' statement. This will get you going.

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Thank you very much for the article. Following your article, I have nearly managed to set up the OAuth login. I do have one problem, though.
    When logging in through the form, I am sent to / and I get the following error:

    "No route found for "GET /""

    Do you have any idea why this might happen?

    ReplyDelete
    Replies
    1. Simon, I am not really sure what is going on but if I had to take a guess:

      * Make sure the form action is correctly defined.
      * Read this section carefully:

      "The redirection to the /oauth/v2/auth will take place using the built in referrer redirect functionality of the Symfony security component. This is an important assumption as the OAuth2 flow will not work if the OAuth2 client makes a request to any URL other than the /oauth/v2/auth endpoint."

      So the browser has to pass the referrer to Symfony2 otherwise the redirect will not work. This tutorial is for demonstration purposes only so you need to address this behavior/implementation in a real application.

      Delete
    2. Hi SimonBS,

      maybe you have to enable the user_referer, so that after login you get redirected to the page that redirected you to login form:

      http://symfony.com/doc/current/cookbook/security/form_login.html#using-the-referring-url

      Delete
  8. Hi Burak,

    just a point about the Resource Owner Password Credentials Grant flow.

    I'm currently using Symfony 2.0.x, so I use the 1.1.3 branch of FOSOauthServerBundle (as it is recommended in the main page).

    In order to use that flow, I had to create a new User Provider (the official Symfony2 docs already tell how to do this) and inject it to the OAuthStorage (vendor/bundles/FOS/OAuthServerBundle/Storage/OAuthStorage.php) class.

    The file that needs to be modified to inject the new service is:

    - vendor/bundles/FOS/OAuthServerBundle/Resources/config/oauth.xml

    The change needed here is to change the line that says:

    null

    (This line is the injection of the user provider that OAuthStorage uses to check for a specific user credenditals as it is called from the OAuth2 library (more precisely from vendor/oauth2-php/lib/OAuth2/OAuth2.php). If you try that flow without making this change, an null pointer (or whatever it is named in PHP) exception occurs)

    I changed this line for the injection of my new created User Provider (identified by "entity_user_provider"):



    The only thing I believe it is not correctly managed are the scopes, as I can't get to use them correctly using this flow.

    Available scopes can be configured in app/config/config.yml:

    fos_oauth_server:
    db_driver: orm #in my case, I use Doctrine with mysql
    client_class: Acme\OAuthServerBundle\Document\Client
    access_token_class: Acme\OAuthServerBundle\Document\AccessToken
    refresh_token_class: Acme\OAuthServerBundle\Document\RefreshToken
    auth_code_class: Acme\OAuthServerBundle\Document\AuthCode
    service:
    options:
    supported_scopes: dummy_scope # now I don't remember how to define more than one scope...
    # ...think this parameter accepts an array, it's a matter of syntax

    Under the "options" tag, you can also define other OAuth2 server parameters (see OAuth2.php for more info), for example "auth_code_lifetime".

    By the moment, I use a hard-coded scope for all the tokens granted within this flow, so I changed the file OAuthStorage.php to manually return a fixed scope (line 148).

    I've been messing with OAuth2 for some (and more) hours, to figure out how to make the Resource Owner Password Credentials Grant flow work, so I hope this information can be useful to anyone that tries to use it.

    Have a nice day.

    ReplyDelete
    Replies
    1. hmpf... for some reason the copy&paste I made from oauth.xml file dosen't appear in the post :(

      the change corresponds to changing the null injection in that file (that corresponds to a null User Provider) for an injection a a new User Provider (google it to know how DI works in Symfony).

      Delete
    2. Hi Toni, please encode your html entities before posting - try this service http://htmlentities.net/. Thank you for the explanation.

      Delete
    3. Ok, thanks for the link!

      So in vendor/bundles/FOS/OAuthServerBundle/Resources/config/oauth.xml i changed this...

      <argument>null</argument>

      ... for this:

      <argument type="service" id="entity_user_provider"/>

      Delete
    4. Maybe you can update the post with this example, so that people can view the example immediately without having to go thorough all the people's comments?

      Delete
    5. For those looking to use a custom user provider (I was using entity), use the console to find the proper service string. I spent far too much time trying to figure it out, so hopefully it saves others time!

      php app/console container:debug --show-private

      You'll get a lot of results so try searching for "security.user.provider"

      Delete
    6. Scope is the fourth argument of finishClientAuthorization(). So you can code in AuthorizeFormHandler::process() :

      return $this->oauth2->finishClientAuthorization(true, $user, $this->request, $this->request->get("scope"));

      Delete
  9. Thanks for documentation Burak. Such a great thing :)

    I still have problems to configure it all. Could anyone take a look at this question I have posted at www.stackoverflow??

    http://stackoverflow.com/questions/12572904/symfony2-security-strange-behavior-challenging-issue

    ReplyDelete
    Replies
    1. Ok, I know what is going on here... but I don't know how to solve it. I would appreciate if anyone could take a look to the issue and to the next explanation.

      It seems like the method explained in this post creates for me sort of a "session" that results to be "parallel" to the real session I want it to create (which would be the good one).

      So, the new login form creates its own session, but it does not create the "official" session of my web site. And that is not what I want. I want the oauth2 method to create one of those official sessions

      Sort of difficult to explain and to understand :(

      I hope someone can help me.

      Delete
  10. In other words, how can I do to make /auth_login form to create as well the main session of my site?

    ReplyDelete
  11. Thanks for this great article. Post sample code on github in few days

    ReplyDelete
  12. There are a few gotchas, but in general this is very good article.

    Thanks for sharing!

    P.S The pattern for the oauth_authorize firewall has to end with a dolar sign, otherwise you will end up in a constant loop.

    Current value: ^/oauth/v2/auth
    Correct value: ^/oauth/v2/auth$

    ReplyDelete
  13. Why is the loginCheckAction() function empty?

    ReplyDelete
    Replies
    1. I'm not sure this should ever get called as the Authentication listener should pickup the 'login check' and FOSUserBundle should handle it.

      Delete
  14. If anyone is getting a "could not load type" it is because in services.xml the arguments for createNamed have switch places, check this discussion for details:

    https://github.com/kwattro/FOSOAuthServerBundle/commit/f437464d91c21bdfab785bb0782087e5f7755dd8

    wasted a few hours and almost gave up with this.. now onto the next problem :)

    ReplyDelete
    Replies
    1. Thanks a lot Roberto, your comment was very helpful :)

      Delete
  15. Hi,

    I have pretty much finish everything but when I try to test and I use this URL :
    /oauth/v2/auth?client_id=3_1anv46pq68kk4c8kcc4kogwggo88co88g8s4woo440wsckc08o&redirect_uri=http%3A%2F%2Fwww.google.com&response_type=token

    I get this error:
    Client 3_1anv46pq68kk4c8kcc4kogwggo88co88g8s4woo440wsckc08o is not found.

    Why isn't it finding it?
    I am trying to search where the string is splitted and the DB request made, but I didn't found it yet.

    ReplyDelete
    Replies
    1. I am using Doctrine ORM.

      I have been searching for the past 2 days and I can't find where it comes from. I don't understand why it can't find the information because it's stored in the DB

      Delete
    2. I've the same error.
      The problem is the query generated from doctrine:
      SELECT t0.random_id AS random_id1, t0.redirect_uris AS redirect_uris2, t0.secret AS secret3, t0.allowed_grant_types AS allowed_grant_types4, t0.id AS id5, t0.name AS name6 FROM api_client t0 WHERE t0.id = '1' AND t0.random_id = 66oucd7jniscwgsgk4wco4ock4cc40o8cgg0g8oo8cg4ckkocw

      66oucd7jniscwgsgk4wco4ock4cc40o8cgg0g8oo8cg4ckkocw is a string and must be "66oucd7jniscwgsgk4wco4ock4cc40o8cgg0g8oo8cg4ckkocw"

      But now I don't know how to solve the problem.
      Any idea?

      Delete
    3. I find the problem.....

      If you use orm and not mongodb, check in AuthorizeController

      namespace ....Bundle\Controller;

      use Symfony\Component\Security\Core\SecurityContext;
      use Symfony\Component\HttpFoundation\Request;
      use FOS\OAuthServerBundle\Controller\AuthorizeController as BaseAuthorizeController;
      use TicketCloud\ApiBundle\Form\Model\Authorize;
      //use TicketCloud\ApiBundle\Document\Client; <----- error
      use TicketCloud\ApiBundle\Entity\Client; <----- correct
      use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;


      Delete
    4. Thank you for the help.

      It corrected my error, but I have another one right after :
      " You have requested a non-existent service "mybundle_oauth_server.authorize.form". "

      Would you happen to know where it comes from?

      Delete
    5. I think it's because I am using yml everywhere.
      So I decided to include the xml ressources to create the services :
      imports:
      - { resource: parameters.yml }
      - { resource: security.yml }
      - { resource: services.yml }
      - { resource: doctrine_extensions.yml }
      - { resource: ../../src/MyBundle/OAuthServerBundle/Resources/config/services.xml } -> I create the services

      Bug now I have an error like this : Fatal error: Declaration of MyBundle\OAuthServerBundle\Form\Type\AuthorizeFormType::buildForm() must be compatible with Symfony\Component\Form\FormTypeInterface::buildForm(Symfony\Component\Form\FormBuilderInterface $builder, array $options) in /home/myuser/mybundle/src/AppMyTaxi/OAuthServerBundle/Form/Type/AuthorizeFormType.php on line 27

      Does anyone had this issue? It is the same when I convert the xml services in yml

      Delete
    6. use Symfony\Component\Form\FormBuilderInterface;
      use Symfony\Component\Form\AbstractType;

      class AuthorizeFormType extends AbstractType
      {
      public function buildForm(FormBuilderInterface $builder, array $options)
      {
      /* some code ... */
      }

      /* more code ... */
      }

      Maybe this code will be helpful

      Delete
  16. Hi,

    when using the url below (of course with appropriate client id and host name), I'm redirected to the authorization page. If I there check the "Allow access" checkbox and submit, I get

    "Catchable Fatal Error: Argument 1 passed to FOS\OAuthServerBundle\Model\Token::setUser() must implement interface Symfony\Component\Security\Core\User\UserInterface, string given"


    http://acme.localhost/app_dev.php/oauth/v2/auth?client_id=4f8e5bb57f8b9a0816000000_1xwgejzp1e3o8sgosc884cgoko44wgg4gc0s84ckw0c0sk4c4s&response_type=code&redirect_uri=http%3A%2F%2Fwww.google.com

    I expected to be redirected to the api login page instead. I have tried to follow up your article but I haven't found any differences between your example and my code.

    ReplyDelete
    Replies
    1. I was experiencing this too. Some debugging in to vendor/friendsofsymfony/oauth-server-bundle/FOS/OAuthServerBundle/Storage/OAuthStorage.php::createAuthCode was show that the $data variable is being passed to AuthCode::setUser was the string 'anon', which hinted that maybe something expired. I relogged in via auth_login and tried again. I got a different error, but his one, at least, was solved. Onward!

      Delete
  17. Hi,
    I got access-token from the client side . Next How can I authenticate my client side with server credentials?

    ReplyDelete
    Replies
    1. I am also very interested to know that.

      It shows different process to create an authentication, but how exactly to user it from client side?

      Delete
    2. I got a solution after a long wandering .
      We can call the secured urls by using access_token query string .
      I just implemented like this http://localhost/symfonyApp/web/app_dev.php/api/users.json?access_token=MDczZjEwMDI2NzUyMzk1MmY0YTVhOGVhOGY4MThjNGNhMTE4NzExYzFhZmU0NzRkZDNiNzI1NTk1ZTRlZmI2YQ

      I am using FOS Rest Bundle . This bundle Simply supporting the authentication process.

      Delete
    3. Thank you, but which authentification flow did you use?

      Implicit Grant Flow ?

      Delete
    4. Hi,
      I am using normal Authorization Code Grant Flow . Not Implicit grant flow.
      1. Authenticating the user
      2. oAuth server return an oauthCode to the call back url
      3.The I request the access token to the server
      4.After I getting the access_toke send a request for the resource (ie . userdetails)

      Delete
    5. Thank you, I am still having some difficulties to make it work (see the previous thread) so I can't try it now.

      Delete
  18. Hi,
    in symfony 2.2 in the service acme_oauth_server.authorize.for must be reversed:
    from
    ...argument type="service" id="acme_oauth_server.authorize.form_type"
    ...argument>acme_oauth_server_authacme_oauth_server_auth
    acme_oauth_server_auth


    ReplyDelete
    Replies
    1. Hi Luigi. I think i have a problem with this service... can you explain better this issue pls?

      Delete
    2. I am also very interested.

      Delete
    3. This comment has been removed by the author.

      Delete
    4. the service acme_oauth_server.authorize.form must be:
      |service id="acme_oauth_server.authorize.form" factory="" ... >
      |argument> acme_oauth_server_auth /argument>
      |argument type="service" id="acme_oauth_server.authorize.form_type" />
      |/service>

      Delete
  19. Hi! Excellent tutorial!! There is any place where i can download the source code? Thanks!!!!

    ReplyDelete
  20. i am using the Authorization Code Grant Flow. Everything is ok, but i haven't a new record on my MongoDB oauthAuthCode collection. did someone has that problem?

    ReplyDelete
  21. Hi,
    Do you have the sample source for this on a github?

    ReplyDelete
    Replies
    1. I've build an example with the Symfony 2.2 SE. https://github.com/mikepage/Symfony2-RESTAPI

      Delete
    2. I am looking forward to check this out.

      Thank you very much !

      Delete
    3. This is the type of example what im looking for!!!.

      +1

      Delete
  22. $ php app/console doctrine:generate:entities MPRestApiBundle

    $ php app/console doctrine:schema:update --force

    Got an error

    Fatal error: Declaration of MP\RestApiBundle\Entity\AccessToken::setUser() must be compatible with that of FOS\OAuthServerBundle\Model\TokenInterface::setUser()

    ReplyDelete
    Replies
    1. Be sure to clean the development cache, I've seen this one before. I will add an SQL dump

      Delete
    2. Am having the same issue ! is there any solution ?

      Delete
    3. Look at the setUser() function in AccessToken, it will be looking for a \Acme\OAuthServerBundle\Entity\User argument, you need to change this to UserInterface, so it looks like : public function setUser(UserInterface $user = null)

      Delete
  23. Hello Robert,
    Did you get any solution for this problem ?

    ReplyDelete
  24. Maybe app/console cache:clear, this example is clean and simple, it should work, as it does for me

    ReplyDelete
    Replies
    1. I've tried that but nothing happend.

      N.B : in the AccessToken::setUser():
      public function setUser(UserInterface $user)
      {
      $this->user = $user;
      }

      Am using my own user Class that extends the FOSUserBundle class. and of course it implements the UserInterface => i think it is the same thing.

      Delete
  25. I've tried that but nothing happend.

    N.B : in the AccessToken::setUser():
    public function setUser(UserInterface $user)
    {
    $this->user = $user;
    }

    Am using my own user Class that extends the FOSUserBundle class. and of course it implements the UserInterface => i think it is the same thing.

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. If you are using symfony 2.1 in AuthorizeFormType you should add

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'data_class' => 'Acme\OAuthServerBundle\Form\Model\Authorize',
    ));
    }

    https://github.com/symfony/symfony/blob/master/UPGRADE-2.1.md

    ReplyDelete
  28. Great Article! It helped me alot.

    BUT I keep getting message "The client credentials are invalid" when I want to request access token via oauth/v2/token.

    I have setup basic http authentication.

    Client creation works and authentication works

    Allow deny form seems to works. I do not get any errors and I get redirected on http://google.com but I am not redirected on http://google.com?code=........

    My problem is that I can not request access token successfully. I am using Symfony 2.3.3.


    Any help?


    Thanks,

    Caslav Sabani

    ReplyDelete
  29. I am having an issue such that when I access:

    http://acme.localhost/oauth/v2/token?client_id=3_5ph2tuyxay048ksksgko4ocs4ss8kssws8k8osgk8o08go0goc&client_secret=28ckpamz5g2s880oggs880cwg0s0css0gsg0o8w0o0ok8skk8k&grant_type=authorization_code&redirect_uri=http%3A%2F%2Fwww.google.com&code=NGEyOWJmZGJjMTdiMmIwN2VmMjNhMTI2ODJjZWU3N2YxYzI1MDRjMDY1NTAzOGRmYTNlZTliMGVmNTZiODI2MQ

    it doesn't redirect me to the login page, instead it goes straight asking me permission to grant to the client id. Why is this?

    ReplyDelete
    Replies
    1. Hello. I've the same issue. The problem is in your oauth_authorize firewall configuration. There is no redirect cuz anonymous is fully allowed. A redirect to the login page will only occur if you've no permissions on the requested URL. So add to following permission.

      - { path: ^/oauth/v2/auth, role: IS_AUTHENTICATED_FULLY }

      This will work for me.

      By the way, great tutorial.

      Cheers.

      Delete
  30. How to request access token with refresh token?

    ReplyDelete
  31. This comment has been removed by the author.

    ReplyDelete
  32. i am getting Could not load type "acme_oauth_server_auth" error , here is the question http://stackoverflow.com/questions/19616387/could-not-load-type-in-symfony

    ReplyDelete
  33. Hi, Thanks for this awesome post. I think I will user your post to implement restful apis. I just have question. You have firewall for /api/* endpoint. Is it possible to have both OAuth base authentication (stateless) and form login based authentication in same firewall. I have been using the same apis for my backbone based application by using cookie for authentication. If so, what would be the kind of securtiy.yml configuration ? Thanks.

    ReplyDelete
  34. Error still continue with FOSOauthServerBundle 1.4.0 and FOSUserBundle 2.0
    {"error":"invalid_client","error_description":"The client credentials are invalid"}

    ==> https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/issues/199
    Someone knows ?

    ReplyDelete
  35. A couple of years later I must tell you that made a typo in the title... "Syfmony"

    ReplyDelete
  36. FOSOAuthServerBundle authentication working, but access_token is “rejected” online
    Can anyone help us out?
    http://stackoverflow.com/questions/21427335/fosoauthserverbundle-symfony2-authentication-working-but-access-token-is-rejec

    ReplyDelete
  37. Hi,
    We are facing issue for Could not load type "acme_myoauth_server_auth", Please help us to resolve this issue.

    ReplyDelete
    Replies
    1. Have you found a solution to this?

      Delete
    2. In case you haven't I have the solution.

      Delete
  38. Hi,
    We are facing issue for Could not load type "acme_myoauth_server_auth", Please help us to resolve this issue.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  39. This comment has been removed by the author.

    ReplyDelete