Let’s assume you already have a project running on Symfony2 with Doctrine2, and you would like to enable some OAuth2 provider functionality on it. In case you still don’t have a running Symfony2 installation, please go through Symfony Book: Installation instructions and get a fresh copy of a Symfony2.
Also your project already, most probably, should has a User Entity, if not you can create something like this one.
<?php// src/Acme/DemoBundle/Provider/UserProvider.phpnamespaceAcme\DemoBundle\Provider;useSymfony\Component\Security\Core\User\UserInterface;useSymfony\Component\Security\Core\User\UserProviderInterface;useSymfony\Component\Security\Core\Exception\UsernameNotFoundException;useSymfony\Component\Security\Core\Exception\UnsupportedUserException;useDoctrine\Common\Persistence\ObjectRepository;useDoctrine\ORM\NoResultException;classUserProviderimplementsUserProviderInterface{protected$userRepository;publicfunction__construct(ObjectRepository$userRepository){$this->userRepository=$userRepository;}publicfunctionloadUserByUsername($username){$q=$this->userRepository->createQueryBuilder('u')->where('u.username = :username OR u.email = :email')->setParameter('username',$username)->setParameter('email',$username)->getQuery();try{$user=$q->getSingleResult();}catch(NoResultException$e){$message=sprintf('Unable to find an active admin AcmeDemoBundle:User object identified by "%s".',$username);thrownewUsernameNotFoundException($message,0,$e);}return$user;}publicfunctionrefreshUser(UserInterface$user){$class=get_class($user);if(!$this->supportsClass($class)){thrownewUnsupportedUserException(sprintf('Instances of "%s" are not supported.',$class));}return$this->userRepository->find($user->getId());}publicfunctionsupportsClass($class){return$this->userRepository->getClassName()===$class||is_subclass_of($class,$this->userRepository->getClassName());}}
Now register the user manager, repository and provider in the Dependency Injection Container
Please pay attention to the user entity namespace, since your User entity might be in other bundle, make sure that namespaces pointing to User entity are correct.
Ok, entities are created, now it’s time to create a separate login page for users coming from OAuth direction. I prefer to use separate login forms to handle this, because usually inside the project you have different redirection policies, and in case of OAuth you strictly need to redirect back to referrer. But of course feel free to reuse your already existing login form inside the project, just make sure it redirects you to the right place then.
Here is the controller responsible for the login form
<?php# src/Acme/DemoBundle/Controller/SecurityController.phpnamespaceAcme\DemoBundle\Controller;useSymfony\Bundle\FrameworkBundle\Controller\Controller;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\Security\Core\SecurityContext;classSecurityControllerextendsController{publicfunctionloginAction(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('AcmeDemoBundle:Security:login.html.twig',array('last_username'=>$lastUsername,'error'=>$error,));}publicfunctionloginCheckAction(Request$request){}}
security:encoders:Acme\DemoBundle\Entity\User:algorithm:sha1encode_as_base64:falseiterations:1role_hierarchy:ROLE_ADMIN:ROLE_USERROLE_SUPER_ADMIN:ROLE_ADMINproviders:user_provider:id:platform.user.providerfirewalls:dev:pattern:^/(_(profiler|wdt)|css|images|js)/security:falselogin:pattern:^/demo/secured/login$security:falseoauth_token:pattern:^/oauth/v2/tokensecurity:falsesecured_area:pattern:^/demo/secured/form_login:provider:user_providercheck_path:_security_checklogin_path:_demo_loginlogout:path:_demo_logouttarget:_demo#anonymous: ~#http_basic:# realm: "Secured Demo Area"oauth_authorize:pattern:^/oauth/v2/authform_login:provider:user_providercheck_path:_security_checklogin_path:_demo_loginanonymous:trueapi:pattern:^/apifos_oauth:truestateless:trueaccess_control:# You can omit this if /api can be accessed both authenticated and anonymously-{path:^/api,roles:[IS_AUTHENTICATED_FULLY]}-{path:^/demo/secured/hello/admin/,roles:ROLE_ADMIN}#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
<?php# src/Acme/DemoBundle/Command/CreateClientCommand.phpnamespaceAcme\DemoBundle\Command;useSymfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;useSymfony\Component\Console\Input\InputArgument;useSymfony\Component\Console\Input\InputOption;useSymfony\Component\Console\Input\InputInterface;useSymfony\Component\Console\Output\OutputInterface;classCreateClientCommandextendsContainerAwareCommand{protectedfunctionconfigure(){$this->setName('acme:oauth-server:client:create')->setDescription('Creates a new client')->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);}protectedfunctionexecute(InputInterface$input,OutputInterface$output){$clientManager=$this->getContainer()->get('fos_oauth_server.client_manager.default');$client=$clientManager->createClient();$client->setRedirectUris($input->getOption('redirect-uri'));$client->setAllowedGrantTypes($input->getOption('grant-type'));$clientManager->updateClient($client);$output->writeln(sprintf('Added a new client with public id <info>%s</info>, secret <info>%s</info>',$client->getPublicId(),$client->getSecret()));}}
If you see response like this one, then we did everything correctly, otherwise, leave a comment, or contact me over twitter, let’s figure this out together ;)