Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Client Credentials
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Meet Brent. He's the hardworking, beard-growing, kale-munching type who has a coop of the nicest, smartest, and best egg-laying chickens this side o' the Mississippi! But feeding his chickens and doing other things around the farm has always taken a lot of time.
But great news! The brand new "Chicken Oversight Operations Platform", or COOP site has just launched! With COOP, you can login to the site and collect your chicken eggs, unlock the barn, and do all kinds of other things just by clicking a button.
Noticing that COOP has an API, Brent wonders if he could write a little script that would collect his eggs automatically. Yea, if he had a script that made an API request on his behalf, he could run it on a CRON job daily and sleep in!
So COOP is real, sort of. You can find this make-believe website by going to http://coop.apps.knpuniversity.com. Go ahead and create an account, and start controlling your virtual farm. It's the future!
Starting our Command-line Script
COOP's API is simple, with just a few endpoints, including the one we want for our little command-line script: eggs-collect.
I've already made a cron/
directory with a script called collect_eggs.php
that'll get us started:
// collect_eggs.php
include __DIR__.'/vendor/autoload.php';
use Guzzle\Http\Client;
// create our http client (Guzzle)
$http = new Client('http://coop.apps.knpuniversity.com', array(
'request.options' => array(
'exceptions' => false,
)
));
Tip
Code along with us! Click the Download button on this page to get the starting point of the project, and follow the readme to get things setup.
It doesn't do anything except create a Client
object that's pointing
at the COOP website. Since we'll need to make HTTP requests to the COOP API,
we'll use a really nice PHP library called Guzzle. Don't worry if you've
never used it, it's really easy.
Before we start, we need to use Composer to download Guzzle.
This tutorial uses an old (version 3) version of Guzzle! It doesn't affect the tutorial, but if you decide to install it manually, be sure to install
guzzle/guzzle
.
Download Composer into the cron/
directory and then install
the vendor libraries:
php composer.phar install
Tip
New to Composer? Do yourself a favor and master it for free: The Wonderful World of Composer.
Let's try making our first API request to /api/2/eggs-collect
. The 2
is our COOP user ID, since we want to collect eggs from our farm. Your
number will be different:
// collect_eggs.php
// ...
$request = $http->post('/api/2/eggs-collect');
$response = $request->send();
echo $response->getBody();
echo "\n\n";
Try it by executing the script from the command line:
php collect_eggs.php
Not surprisingly, this blows up!
{
"error": "access_denied",
"error_description": "an access token is required"
}
OAuth Applications
But before we think about getting a token, we need to create an application on COOP (http://coop.apps.knpuniversity.com/api - click "Your Applications" and then "Create your Application"). The application represents the external app or website that we want to build. In our case, it's the little command-line script. In OAuth-speak, it's this application that will actually ask for access to a user's COOP account.
Give it a name like "Brent's Lazy CRON Job", a description, and check only the box for "Collect Eggs from Your Chickens". These are "scopes", or basically the permissions that your app will have if a token is granted from COOP.
When we finish, we now have a Client ID and an auto-generated "Client Secret". These are a sort of username and password for the application. One tricky thing is that the terms "application" and "client" are used interchangeably in OAuth. And both are used to refer to the application we just registered and the actual app you're building, like the CRON script or your website. I'll try to clarify along the way.
Now, let's get an access token!
Client Credentials Grant Type
The first OAuth grant type is called Client Credentials, which is the simplest of all the types. It involves only two parties, the client and the server. For us, this is our command-line script and the COOP API.
Using this grant type, there is no "user", and the access token we get will only let us access resources under the control of the application. When we make API requests using this access token, it's almost like we're logging in as the application itself, not any individual user. I'll explain more in a second.
If you visit the application you created earlier, you'll see a nice "Generate a Token" link that when clicked will fetch one. Behind the scenes, this uses client credentials, which we'll see more closely in a second.
http://coop.apps.knpuniversity.com/token
?client_id=Your+Client+Name
&client_secret=abcdefg
&grant_type=client_credentials
But for now, we can celebrate by using this token immediately to take actions on behalf of the application!
Access Tokens in the API
Exactly how to do this depends on the API you're making requests to. One common method, and the one COOP uses, is to send it via an Authorization Bearer header.
GET /api/barn-unlock HTTP/1.1
Host: coop.apps.knpuniversity.com
Authorization: Bearer ACCESSTOKENHERE
Update the script to send this header:
// collect-eggs.php
// ...
$accessToken = 'abcd1234def67890';
$request = $http->post('/api/2/eggs-collect');
$request->addHeader('Authorization', 'Bearer '.$accessToken);
$response = $request->send();
echo $response->getBody();
echo "\n\n";
When we run the script again, start celebrating, because it works! And now we have enough eggs to make an omlette :)
{
"action": "eggs-collect",
"success": true,
"message": "Hey look at that, 3 eggs have been collected!",
"data": 3
}
Trying to Collect Someone Else's Eggs
Notice that this collects the eggs for our user becase we're including our user ID in the URL. What happens if we change id to be for a different user?
/api/3/eggs-collect
If you try it, it fails!
{
"error": "access_denied",
"error_description": "You do not have access to take this action"
}
Technically, with a token from client credentials, we're making API requests not on behalf of a user, but on behalf of an application. This makes client credentials perfect for making API calls that edit or get information about the application itself, like a count of how many users it has.
We decided to build COOP so that the application also has access to modify the user that created the application. That's why we are able to collect our user's eggs, but not our neighbor's.
Getting the Token via Client Credentials
Put the champagne away: we're not done yet. Typically, access tokens don't last forever. COOP tokens last for 24 hours, which means that tomorrow, our script will break.
Letting the website do the client-credentials work for us was nice for testing, but we need to do it ourselves inside the script. Every OAuth server has an API endpoint used to request access tokens. If we look at the COOP API Authentication docs, we can see the URL and the POST parameters it needs:
http://coop.apps.knpuniversity.com/token
Parameters:
client_id
client_secret
grant_type
Let's update our script to first make this API request. Fill in the client_id
,
client_secret
and grant_type
POST parameters:
// collect-eggs.php
// ...
// run this code *before* requesting the eggs-collect endpoint
$request = $http->post('/token', null, array(
'client_id' => 'Brent\'s Lazy CRON Job',
'client_secret' => 'a2e7f02def711095f83f2fb04ecbc0d3',
'grant_type' => 'client_credentials',
));
// make a request to the token url
$response = $request->send();
$responseBody = $response->getBody(true);
var_dump($responseBody);die;
// ...
With any luck, when you run it, you should see a JSON response with an access token and a few other details:
{
"access_token": "fa3b4e29d8df9900816547b8e53f87034893d84c",
"expires_in": 86400,
"token_type": "Bearer",
"scope": "chickens-feed"
}
Let's use this access token instead of the one we pasted in there:
// collect-eggs.php
// ...
// step1: request an access token
$request = $http->post('/token', null, array(
'client_id' => 'Brent\'s Lazy CRON Job',
'client_secret' => 'a2e7f02def711095f83f2fb04ecbc0d3',
'grant_type' => 'client_credentials',
));
// make a request to the token url
$response = $request->send();
$responseBody = $response->getBody(true);
$responseArr = json_decode($responseBody, true);
$accessToken = $responseArr['access_token'];
// step2: use the token to make an API request
$request = $http->post('/api/2/eggs-collect');
$request->addHeader('Authorization', 'Bearer '.$accessToken);
$response = $request->send();
echo $response->getBody();
echo "\n\n";
Now, it still works and since we're getting a fresh token each time, we'll never have an expiration problem. Once Brent sets up a CRON job to run our script, he'll be sleeping in 'til noon!
Why, What and When: Client Credentials
Every grant type eventually uses the /token
endpoint to get a token, but
the details before that differ. Client Credentials is a way to get a token
directly. One limitation is that it requires your client secret, which is
ok now because our script is hidden away on some server.
But on the web, we won't be able to expose the client secret. And that's where the next two grant types become important.
58 Comments
Hi kenton_2233!
Yes, let me see if I can make this sentence more clear :).
When you authenticate via "client credentials", you are not really "authenticating" or "logging in" as a specific user. You're actually "authenticating" as the "app" you created. In this example, we went to the COOP site and created an app called "Brent's Lazy CRON Job". So when we authenticate via "client credentials", we are really "authenticating" or logging in *as* that "app" - not as our user. This normally means that we would only have access to view and modify settings on our app - but NOT to do things that our user account normally could do (e.g. collect eggs). Thus, "client credentials" is often pretty limiting.
So, when our "imaginary" company created COOP, we had to make a decision: if someone authenticates via "client credentials", should they *only* have access to modify the application, or should we *also* allow them to take actions on behalf of the user who owns the application? In this sentence, I'm describing that we (creators of the COOP site) decided to make our OAuth server a bit more flexible so that you *can* take user actions even though you're only authenticating with "client credentials". In other OAuth servers, this might not be true. For example, if you use client credentials to authenticate with Facebook, you don't have access to some of the normal things you do with a normal access token. For example, I believe that you cannot use the /me endpoint (because you're not authenticated as a user!) but you *can* pull some stats from your application.
I hope this helps!
Here's a version of the whole thing for GuzzleHttp/Guzzle version 6, which might require updating the composer.json file as follows:
```
{
"require": {
"guzzlehttp/guzzle": "~6.2",
"psr/log": "^1.0"
},
"minimum-stability": "dev"
}
```
Then running composer update.
```
include __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Client;
// create our http client (Guzzle)
$client = new Client(['base_uri' => 'http://coop.apps.knpunivers...', 'defaults' => [
'exceptions' => false ]]);
$res = $client->request('GET', 'http://coop.apps.knpunivers...', array(
'request.options' => array(
'exceptions' => true,
)
));
echo $res->getStatusCode();
echo "\n\n";
$http = new Client(['base_uri' => 'http://coop.apps.knpunivers...', 'defaults' => [
'exceptions' => false ]]);
$response = $http->request('POST', '/token', [
'form_params' => [
'client_id' => 'ABC',
'client_secret' => 'credential',
'grant_type' => 'client_credentials'
]
]);
$responseBody = $response->getBody(true);
$responseArr = json_decode($responseBody, true);
$accessToken = $responseArr['access_token'];
// step2: use the token to make an API request
$headers = array('Authorization' => 'Bearer '.$accessToken);
$response = $http->request('POST', '/api/2027/eggs-collect', array(
"headers" => $headers
));
echo $response->getBody();
echo "\n\n";
```
For information, the code given in this tutorial works with GuzzleHTTP 3 or earlier. On later version, it is very different.
Here's what I did to make it work:
include __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client( [
'base_uri' => 'http://coop.apps.knpunivers...',
'defaults' => [
'exceptions' => false,
]
]);
$response = $client->post( 'api/2/eggs-collect', [
'headers' => [
'Authorization' => 'Bearer tokensdcsdcsdc66561'
]
]);
echo $response->getBody();
Thanks for posting that Sébastien Rosset! You're absolutely right - and fortunately, version 3 of Guzzle is still available (but getting quite old). But yes - if you are ok re-working some of your Guzzle code (just like you did), then feel free to use the new version :).
Cheers!
Why is that I am having SSL problems connecting to this site?
coop.apps.symfonycasts.com I see it listed in google or Bing and click on the link, but I get SSL error messages.
Hey @captsulu32
Yes, you're right the domain does not have an SSL certificate, it's just an internal app we use for this tutorial. You should be fine by allowing your browser to still access that site. Let me know if you find any issues going through this tutorial
Cheers!
This course has to be marked as deprecated or obsolete since many changes have happened since its release.
Hey AbelardoLG,
Thank you for your interest in SymfonyCasts tutorials!
Could you give us a bit more information about what exactly changed? Yes, this course is old, and yes, it's based on some old PHP dependencies, but the concepts we teach in this course related to OAuth 2 are still valid for today and still used in literally all platforms like Google, Facebook, etc.
Cheers!
Hey Victor.
First of all, sorry for replying too late!
I tried to reproduce this course but Guzzle changed its API, methods, and so on.
I am stuck with this video.
Best regards and my best wishes to Symfony Casts staff!
Hey AbelardoLG,
Yeah, major versions of libraries we use in the course might lead some BC breaks, I'd recommend you to download the course code from this page and start from the start/ folder following instructions in the README.md file inside. This way you would be able to follow the course and match the code in the video.
Cheers!
Hey @Akshit!
Ah, sorry you're having problems! Indeed, the chapter *should* be marked as complete when the video is completely viewed (or, more technically, when you view, I think, beyond around 90% of the video, it is marked as completed). But there is no feature currently to mark the chapter is viewed by viewing the script. Is that the part that you would like to see? We're always collecting feedback to help us improve - so please let me know!
Cheers!
I just bought this course and http://coop.apps.knpunivers... doesnt seems to work
I only receive a http response 522
Hey Peder N.
Thanks for informing us about that problem. I'm happy to tell you that it's already fixed, you should be able to keep going with this tutorial. I'm sorry for any inconvenience
Please, remember to change the baseUrl to 'http://coop.apps.symfonycas...
If you don’t do so, you should get a "405 Method Not Allowed" error.
This is happening because 'http://coop.apps.knpunivers...' is trying to redirect to 'http://coop.apps.symfonycas... through a GET request.
Hi Julia V.
Yes it was not perfect redirect, but we fixed it. Now everything works good!
Cheers!
Hello,
I'm stuck in the very begining. When i try to send the request with command "php collect_eggs.php" i get "No route found for "POST /application/api/api/25/eggs-collect"
Please help
Sorry, stupid mistake with slash in a wrong place. Solved :)
Hey Lukasz,
I'm glad you got it working so fast by yourself.
Cheers!
When I try to run the code php collect_eggs.php, It is showing the php code, not the response in braces { }. Please help. I tried this in both Windows and Fedora, thank you.
Hey Mohammed Majid Ali!
Hmm. Make sure you have the`
<?php`
line at the top of your PHP file. It sounds like you may be missing this :).
Cheers!
$request = $client->post('/token', null, array(
'grant_type' => 'client_credentials',
'client_id' => "Daniel's CRON Job",
'client_secret' => 'KEY',
));
Is there something wrong with this request? I keep getting a 400 response:
{"error":"invalid_request","error_description":"The grant type was not specified in the request"}
When I comment that section out it works fine. I'm not sure what's going wrong.
Hey Serdan1689 ,
Please, check out Ryan's explanation on the similar comment below: https://knpuniversity.com/screencast/oauth/client-credentials#comment-2802524257 . What REST client are you using here?
Cheers!
Hi guys,
I want to use Google Analytics for my website (to embed a nice looking graph, e.g. with Chart.js, which should show the unique pageviews for the last 30 days ). For this, I thought it would be good to use client credentials.
I have problems to receive the access token, since I believe Google does not support 'grant_type' => 'client_credentials'? Is this right? When i use Google OAuth 2.0 Playground and use their token, the GET-Request https://www.googleapis.com/... in the second step works totally fine for me. The problem consists in fetching the access token in the first step.
Anyways this is how my code looks like in testing.php:
require __DIR__.'/vendor/autoload.php';
$http = new \Guzzle\Http\Client([
'defaults' => [
'exceptions' => false,
]
]);
$request = $http->post('https://www.googleapis.com/..., null, array(
'client_id' => 'XXXX',
'client_secret' => 'XXXX',
'grant_type' => 'client_credentials',
));
$response = $request->send();
--> HERE it fails ( Client error response, [status code] 400, [reason phrase] Bad Request )
$responseBody = $response->getBody(true);
$responseArr = json_decode($responseBody, true);
$accessToken = $responseArr['access_token'];
$request = $http->get('https://www.googleapis.com/...
$request->addHeader('authorization', 'Bearer '.$accessToken);
$query = $request->getQuery();
$query->set('ids', 'XXXX');
$query->set('start-date', '30daysAgo');
$query->set('end-date', 'yesterday');
$query->set('metrics', 'ga:pageviews');
$query->set('metrics', 'ga:uniquePageviews');
$response = $request->send();
echo $response->getBody();
echo "\n\n";
Or is there another way?
Thank you for your help,
Chris
Hey Chris!
Yes, you're right. You'll want to use the <a href="https://github.com/google/google-auth-library-php">google auth library </a> to make this call. What you're doing looks mostly correct, but rather than using Client Credentials, you'll use Service Account Credentials. Download a service account credentials file from the <a href="https://console.developers.google.com/apis/credentials">Developer Console</a> and then do the following:
require __DIR__.'/vendor/autoload.php';
use Google\Auth\CredentialsLoader;
// this is downloaded from the Developer Console
$credentialsFile = '/path/to/your_service_account_credentials.json';
// JSON-decode the credentials into a PHP array
$jsonKey = json_decode(file_get_contents($credentialsFile), true);
// Define the scope required to access analytics data
$scopes = ['https://www.googleapis.com/auth/analytics'];
// Create a credentials object for using a service account
$credentials = CredentialsLoader::makeCredentials($scopes, $jsonKey);
// Create a Guzzle HTTP client from those credentials
$http = CredentialsLoader::makeHttpClient($credentials);
// Make an authenticated request to the API!
$response = $http->post('https://www.googleapis.com/...);
Hey Brent Shaffer,
thank you very much for your help. Now it`s working fine.
For those who are interested, but still struggling (This refers especially to the Google analytics api): 1. Make sure you download the service account credentials file. Nothing else. 2. In Google Analytics under administration>user-administration you have to grant permission to the beforehand created Service Account, otherwise you will get an error.
The google auth library is easy to implement, but unfortunately I went the wrong way first :)
Thank you for your work!
Chris
What technologies were used to create the coop api/oauth app and api docs/sandbox?
Yo Kelvin Dijkstra!
I could tell you, or I could show you :). You can find the code here: https://github.com/knpunive.... It's a simple Silex application, which leverages our co-author's OAuth server library: https://github.com/bshaffer.... The API docs (I assume you basically mean this page: http://coop.apps.knpunivers... are, unfortunately, just manually created in the Silex app: the API is so simple that we could do this (and we wanted it to look fun and be user-friendly).
If you have any other questions, let me know!
Cheers!
weaverryan Thanks! The api looked nice and user-friendly. I've done stuff with swagger before but I liked the "feel" of your docs so I wanted to see if it was custom or something automated (like swagger)
Hi, where can I download the start project? I can't find it.
I guess I got the start project from Github, thanks!
Hi Rex!
Yep, that's one way - glad you figured that out :). There's also a download link on the upper right of this page if you're a subscriber.
Cheers!
How to use it with FOSOAuthServerBundle for symfony 3? I couldn't find any explanations or tutorials... Could you, please, provide some help?
Hey Andjii!
We don't talk about FOSOAuthServerbundle on any tutorials, but that might be great idea for a tutorial! Is the documentation unclear? Or are you stuck on anything specifically? This tutorial talks entirely about the *client* side of OAuth (not the server) - but it should help make OAuth in general a lot more clear (which is important for using OAuth as a client or a server).
Cheers!
Hi Guys.
If one of you have problems getting 400 response.
This is of the way that worked fine for me.
I used Guzzle 6.
use GuzzleHttp\Client;
$http = new Client(['base_uri' => 'http://coop.apps.knpuniversity.com', 'defaults' => [
'exceptions' => false ]]);
$response = $http->request('POST', '/token', [
'form_params' => [
'client_id' => 'Your Id',
'client_secret' => 'Your Secret',
'grant_type' => 'client_credentials'
]
]);
echo $response->getBody();
Yo Julián May!
Thanks for sharing :). Just to be clear for others - be careful with that `exceptions => false` option - it "suppresses" the error when you have a 400 error. So, if you use it - just realize that if you get a 400 error, it might *look* like it's working, when it's actually not.
Cheers!
Hi, did you change something on your page http://coop.apps.knpunivers... ?
I have generated a token on the web site and tried to collect eggs with this token. But then, I receive "The request requires higher privileges than provided by the access token"
Hey einue!
We haven't changed anything on the site... at least not on purpose :). But, I do have on idea. Go to your the Applications tab (http://coop.apps.knpunivers... and click to edit/view your application. Under the "Scope", do you see "eggs-collect"? If not, then it means that you have not authorized access keys created for this app to be able to take this action.
Cheers!
Thank you! This was my mistake. ;-)
You are talking about installing the vendors with composer, but I can't find your requirements.
I have the impression you start from existing files, where can I find them if it is the case ?
Hey Pierre!
Yes, we do start with some files - if you're a subscriber, there's a Download button on this page that has the stuff you'll need. That download comes with a start directory and a finish directory - we're working in the start.
If you don't have a subscription, you can still get the start code directly form the repository: https://github.com/knpunive....
Does that help?
Cheers!
It sure does, thanks. Maybe you should mention the fact that one needs to subscribe to get the files, for those coming from a Google search like me.
Will do - it's not smooth yet - I probably would have had the same question as you :)
Thanks!!
Hey folks,
For those who have the error "The grant type was not specified in the request".
The video is done with an old Guzzle version.
Here is a snippet with a new one.
Thanks for sharing Souhail Merroun!
weaverryan a pleasure :)
Hi Souhail Merroun,
Request api for eggs-collect is only working with "POST" on me. I got "405 method not allowed" response when I try with "GET".
Thanks for sharing.
Guzzle seems to have changed a lot. The current version is Guzzle 6. The way it is used has also changed. Would you please add more information on how to use Guzzle 6? It is just a little confusing. I am looking for some information on OAuth2 for Nodejs.
"Houston: no signs of life"
Start the conversation!
Can you please explain what is the meaning of this sentence? Sorry, I am not able to understand it.
We decided to build COOP so that the application also has access to modify the user that created the application. That's why we are able to collect our user's eggs, but not our neighbor's.
It appears to be contradictory.