11/01/2016 - SYMFONY
This example shows us how to create an OAuth2 API client with symfony and depends on OAuth2 API server we touched upon in previous posts so you better read up on it as well. It authenticates with password
grant type. Since the API uses role_hierarchy
to control controller access with @Security("has_roles('xxxxx')")
in annotations, password
grant type is the best/simplest option to use. See the reference links at the bottom of the post.
In example below, I expose client_id
and client_secret
in request URI to obtain access token for demonstration purposes. You should not do that in real world applications. Instead, you should encode both of them with base64_encode
function and add it to request header as Authorization
.
$clientId = 'i-am-client-id';
$clientSecret = 'i-am-client-secret';
$base64 = base64_encode($clientId.':'.$clientSecret);
$header = 'Basic '.$base64;
So you should use it like Authorization: Basic aS1hbS1jbGllbnQtaWQ6aS1hbS1jbGllbnQtc2VjcmV0
in your request. On top of that, you should remove username
, password
and grant_type
from URI to send them as parameters encoded with application/x-www-form-urlencoded
. Your final request should look like the one below.
curl -X POST
-H 'Authorization: Basic aS1hbS1jbGllbnQtaWQ6aS1hbS1jbGllbnQtc2VjcmV0'
-H 'content-type: application/x-www-form-urlencoded'
-d 'grant_type=password&username=inanzzz&password=123123'
http://oauth-server.dev/app_dev.php/oauth/v2/token
refresh_token
by default.access_token
contains "user" information association with it, therefore there will be "user" information retrieved in server/API site.access_token
and refresh_token
can be obtained by exchanging the current refresh_token
in a request.access_token
and it doesn't cause redirect after authentication unlike Authorization Code grant type.Install "guzzlehttp/guzzle": "6.1.1"
package.
Sensitive info in URI below is given to you by API administrator.
# oauth-client/app/config/parameters.yml
parameters:
oauth_api_access_token_cache_namespace: OAUTH2_ACCESS_TOKEN
oauth_api_base_url: http://oauth-server.dev/app_dev.php
oauth_api_uri_version: /1
oauth_api_token_uri: /oauth/v2/token?client_id=1_fqnumqc8gvkss8soo44g4g0sw0s0okkk8og84k8ggckwsk4gc&client_secret=1m5d8yl0wk00wogsskoooo4k4o0wgw8k00cw8k00s4g8w8gkw0&grant_type=password&username=inanzzz&password=123123
# oauth-client/src/Application/ClientBundle/Resources/config/controllers.yml
services:
application_client.controller.team:
class: Application\ClientBundle\Controller\TeamController
arguments:
- %oauth_api_access_token_cache_namespace%
- %oauth_api_base_url%
- %oauth_api_uri_version%
- %oauth_api_token_uri%
This example will generate a new access_token
per request so the requests will be slow. To solve this issue, you can store access_token
or refresh_token
in cache and regenerate it when it is expired. Also putting whole logic in controller is a bad practise. You should divide it into classes like service, model, factory, helper so on.
namespace Application\ClientBundle\Controller;
use Exception;
use GuzzleHttp\Client;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* @Route("team", service="application_client.controller.team")
*/
class TeamController extends Controller
{
private $oauthApiAccessTokenCacheNamespace;
private $oauthApiBaseUrl;
private $oauthApiUriVersion;
private $oauthApiTokenUri;
public function __construct(
$oauthApiAccessTokenCacheNamespace,
$oauthApiBaseUrl,
$oauthApiUriVersion,
$oauthApiTokenUri
) {
$this->oauthApiAccessTokenCacheNamespace = $oauthApiAccessTokenCacheNamespace;
$this->oauthApiBaseUrl = $oauthApiBaseUrl;
$this->oauthApiUriVersion = $oauthApiUriVersion;
$this->oauthApiTokenUri = $oauthApiTokenUri;
}
/**
* @param string $name
*
* @Method({"GET"})
* @Route("/{name}")
*
* @return Response
*/
public function getTeamAction($name)
{
$accessToken = $this->getAccessToken();
$response= $this->call(
'GET',
$this->oauthApiUriVersion.'/server/team/'.$name,
$accessToken
);
return new Response($response->getBody().' with ACCESS TOKEN: '.$accessToken);
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("")
*
* @return Response
*/
public function createTeamAction(Request $request)
{
$accessToken = $this->getAccessToken();
$response= $this->call(
'POST',
$this->oauthApiUriVersion.'/server/team',
$accessToken,
$request->getContent()
);
return new Response($response->getBody().' with ACCESS TOKEN: '.$accessToken);
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("/club")
*
* @return Response
*/
public function createClubAction(Request $request)
{
$accessToken = $this->getAccessToken();
$response= $this->call(
'POST',
$this->oauthApiUriVersion.'/server/club',
$accessToken,
$request->getContent()
);
return new Response($response->getBody().' with ACCESS TOKEN: '.$accessToken);
}
private function getAccessToken()
{
$response = $this->call('GET', $this->oauthApiTokenUri);
$responseParts = json_decode($response->getBody(), true);
return $responseParts['access_token'];
}
private function call($method, $uri, $auth = null, $postData = null)
{
$client = new Client();
try {
return $client->request(
$method,
$this->oauthApiBaseUrl.$uri,
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer '.$auth
],
'body' => $postData
]
);
} catch (Exception $e) {
$message = $e->getCode() == 403 ? 'Missing role.' : $e->getMessage();
throw new AccessDeniedHttpException($message);
}
}
}
# oauth-server/src/Application/ServerBundle/Controller/ServerController.php
/**
* @param string $name
*
* @Method({"GET"})
* @Route("/team/{name}")
*
* @return Response
*/
public function getTeamAction($name)
{
return new Response(sprintf('GET your team [%s] from Server', $name));
}
/**
* @param Request $request
*
* @Security("has_role('ROLE_ADMIN')")
* @Method({"POST"})
* @Route("/team")
*
* @return Response
*/
public function createTeamAction(Request $request)
{
$postData = json_decode($request->getContent(), true);
return new Response(sprintf('POST your team [%s] to Server', $postData['name']));
}
/**
* @param Request $request
*
* @Security("has_role('ROLE_SUPER_ADMIN')")
* @Method({"POST"})
* @Route("/club")
*
* @return Response
*/
public function createClubAction(Request $request)
{
$postData = json_decode($request->getContent(), true);
return new Response(sprintf('POST your club [%s] to Server', $postData['name']));
}
This is how the API owner created our user. Unfortunately we're not given ROLE_SUPER_USER
role although there is a controller method above.
MacBook-Pro:oauth-server inanzzz$ php app/console create:oauth:user --username=inanzzz --password=123123 --email=myemail@mydomain.com --role=ROLE_USER --role=ROLE_ADMIN
OAuth user has been created...
Username: inanzzz
Password: 123123
Roles: ["ROLE_USER","ROLE_ADMIN"]
# Request
GET http://oauth-client.dev/app_dev.php/team/inanzzz
# Response
GET your team [inanzzz] from Server with ACCESS TOKEN: YjQ0ZjVhMDE4MmRhMDIyYWQyMzhhODM4M2YzMGRmMzc0ODI2ZWU4NWFiMmJhZGUyOTQ0OTA3Y2MyNDhkMzYyMw
# Request
POST http://oauth-client.dev/app_dev.php/team
{
"name": "inanzzz"
}
# Response
POST your team [inanzzz] to Server with ACCESS TOKEN: ZTllODdhZTRlY2VmYzdhYmU4ZmI5MjUxMDQ1MjI0YjMzZjAxN2E3YzQxZmUwNjljMDMyZjg1OTZhODUwMGI0ZA
The reason why the one below didn't work is because if you look at the command how the user was created didn't actually assign ROLE_SUPER_ADMIN
to the user.
# Request
POST http://oauth-client.dev/app_dev.php/team/club
{
"name": "inanzzz"
}
# Response
403 Forbidden - AccessDeniedHttpException
Missing role.
For more information, you can check links below.