If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeSuddenly, Brent is jolted awake at noon to the sound of farmer Scott driving his eggs to the market and screaming:
Haha, Brent! My chickens lay way more eggs than yours!
But in reality, Brent knows that his chickens are way better egg-making hens than Scott's... but how to prove it?
Then it hits him! The COOP API has an endpoint to see how many eggs have been collected from a user's farm each day. Brent decides to create a new website that will use this endpoint to count how many total eggs a COOP user's farm has collected. He'll call it: Top Cluck! Fantasy Chicken League, or FCL for short. To call the /api/eggs-count
endpoint on behalf of each user, the site will use OAuth to collect an access token for every farmer that signs up.
Once again, the question is: how can each user give FCL an access token that allows it to count eggs on their behalf?
Let's check out the FCL app, which Brent has already started building. It lives in the client/
directory of the code download. I'll use the built-in PHP web server to run this site:
cd client/web php -S localhost:9000
Tip
Code along with us! Click the Download link on this page to get the starting point of the project.
That command starts a built-in PHP webserver, and it'll just sit right there until we're ready to turn it off. This project also uses Composer, so let's copy the composer.phar
file we used earlier into this directory and use install to download some outside libraries the project uses.
Tip
If this doesn't work and PHP simply shows you its command-line options, check your PHP version. The built-in web server requires PHP 5.4 or higher.
Put the URL to the site into your browser and load it up. Welcome to Top Cluck! We already have a leaderboard and basic registration. Go ahead and create an account, which automatically logs us in.
The site is fully-functional, with database tables ready to keep track of how many eggs each farmer has collected. The only missing piece is OAuth: getting the access token for each user so that we can make an API request to count their eggs.
Before TopCluck can make an API request to COOP to count my eggs, I need to authorize it. On the homepage, there's already an Authorize link, which just prints a message right now.
The code behind this URL lives in the src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
file. You don't even need to understand how this works, just know that whatever we do here, shows up:
// src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
// ...
public function redirectToAuthorization(Request $request)
{
die('Hallo world!');
}
The first step of the authorization code grant type is to redirect the user to a specific URL on COOP. From here the user will authorize our app. According to COOP's API Authentication page, we need to redirect the user to /authorize
and send several query parameters.
Tip
If you're using Symfony, check out knpuniversity/oauth2-client-bundle. This library helps you interact with an OAuth2 server using the "authorization code" grant type.
In our code, let's start building the URL:
// src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
// ...
public function redirectToAuthorization(Request $request)
{
$url = 'http://coop.apps.knpuniversity.com/authorize?'.http_build_query(array(
'response_type' => 'code',
'client_id' => '?',
'redirect_uri' => '?',
'scope' => 'eggs-count profile'
));
var_dump($url);die;
}
The response_type
type is code
because we're using the Authorization Code flow. The other valid value is token
, which is for a grant type called implicit flow. We'll see that later.
For scopes
, we're using profile
and eggs-count
so that once we're authorized, we can get some profile data about the COOP user and, of course, count their eggs.
To get a client_id
, let's go to COOP and create a new application that represents TopCluck. The most important thing is to check the 2 boxes for profile information and collecting the egg count. I'll show you why in a second.
Tip
If there is already an application with the name you want, just choose
something different and use that as your client_id
.
Copy the client_id
into our URL. Great! The last piece is the redirect_uri
, which is a URL on our site that COOP will send the user to after granting or denying our application access. We're going to do all kinds of important things once that happens.
Let's set that URL to be /coop/oauth/handle
, which is just another page that's printing a message. The code for this is right inside the same file, a little further down:
// src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
// ...
public function receiveAuthorizationCode(Application $app, Request $request)
{
// equivalent to $_GET['code']
$code = $request->get('code');
die('Implement this in CoopOAuthController::receiveAuthorizationCode()');
}
Instead of hardcoding the URL, I'll use the URL generator that's part of Silex:
public function redirectToAuthorization(Request $request)
{
$redirectUrl = $this->generateUrl('coop_authorize_redirect', array(), true);
$url = 'http://coop.apps.knpuniversity.com/authorize?'.http_build_query(array(
'response_type' => 'code',
'client_id' => 'TopCluck',
'redirect_uri' => $redirectUrl,
'scope' => 'eggs-count profile'
));
// ...
}
However you make your URL, just make sure it's absolute. Ok, we've built our authorize URL to COOP, let's redirect the user to it:
public function redirectToAuthorization(Request $request)
{
// ...
return $this->redirect($url);
}
That redirect()
function is special to my app, so your code may differ. As long as you somehow redirect the user, you're good.
Tip
Since we're using Silex, the redirect()
function is actually a shortcut
I created to create a new RedirectResponse
object.
Let's try it! Go back to the homepage and click the "Authorize" link. This takes us to our code, which then redirects us to COOP. We're already logged in, so it gets straight to asking us to authorize the app. Notice that the scopes that we included in the URL are clearly communicated. Let's authorize the app. Later, we'll see what happens if you don't.
When we click the authorization button, we're sent back to the redirect_uri
on TopCluck! Nothing has really happened yet. COOP didn't set any cookies or anything else. But the URL does include a code
query parameter.
This query parameter is called the authorization code, and it's unique to this grant type. It's not an access token, which is really what we want, but it's the key to getting that. The authorization code is our temporary proof that the user said that our application can have an access token.
Let's start by copying the code from the collect_eggs.php
script and pasting it here. Go ahead and change the client_id
and client_secret
to be from the new client or application we created for TopCluck:
// src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
// ...
public function receiveAuthorizationCode(Application $app, Request $request)
{
// equivalent to $_GET['code']
$code = $request->get('code');
$http = new Client('http://coop.apps.knpuniversity.com', array(
'request.options' => array(
'exceptions' => false,
)
));
$request = $http->post('/token', null, array(
'client_id' => 'TopCluck',
'client_secret' => '2e2dfd645da38940b1ff694733cc6be6',
'grant_type' => 'authorization_code',
));
// make a request to the token url
$response = $request->send();
$responseBody = $response->getBody(true);
var_dump($responseBody);die;
}
If we look back at the COOP API Authentication docs, we'll see that /token
has 2 other parameters that are used with the authorization grant type: code
and redirect_uri
. I'm already retrieving the code
query parameter, so let's fill these in. Make sure to also change the grant_type
to authorization_code
like it describes in the docs. Finally, dump the $responseBody
to see if this request works:
public function receiveAuthorizationCode(Application $app, Request $request)
{
// equivalent to $_GET['code']
$code = $request->get('code');
// ...
$request = $http->post('/token', null, array(
'client_id' => 'TopCluck',
'client_secret' => '2e2dfd645da38940b1ff694733cc6be6',
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->generateUrl('coop_authorize_redirect', array(), true),
));
// ...
}
The key to this flow is the code
parameter. When COOP receives our request, it will check that the authorization code is valid. It also knows which user the code belongs to, so the access token it returns will let us make API requets on behalf of that user.
But what about the redirect_uri
? This parameter is absolutely necessary for the API request to work, but isn't actually used by COOP. It's a security measure, and it must exactly equal the original redirect_uri
that we used when we redirected the user.
Ok, let's try it! When we refresh, the API actually gives us an error:
{
"error": "invalid_grant",
"error_description": "The authorization code has expired"
}
The authorization code has a very short lifetime, typically measured in seconds. We normally exchange it immediately for an access token, so that's ok! Let's start the whole process from the homepage again.
Tip
Usually, an OAuth server will remember that a user already authorized an app and immediately redirect the user back to your app. COOP doesn't do this only to make things easier to understand.
This time, the API request to /token
returns an access_token
. Woot! Let's also set expires_in
to a variable, which is the number of seconds until this access token expires:
public function receiveAuthorizationCode(Application $app, Request $request)
{
// ...
$request = $http->post('/token', null, array(
'client_id' => 'TopCluck',
'client_secret' => '2e2dfd645da38940b1ff694733cc6be6',
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->generateUrl('coop_authorize_redirect', array(), true),
));
// make a request to the token url
$response = $request->send();
$responseBody = $response->getBody(true);
$responseArr = json_decode($responseBody, true);
$accessToken = $responseArr['access_token'];
$expiresIn = $responseArr['expires_in'];
}
Just like in our CRON script, let's use the access token to make an API request! One of the endpoints is /api/me
, which returns information about the user that is tied to the access token. Let's make a GET request to this endpoint, setting the access token on the Authorization
header, just like we did before:
public function receiveAuthorizationCode(Application $app, Request $request)
{
// ...
$accessToken = $responseArr['access_token'];
$expiresIn = $responseArr['expires_in'];
$request = $http->get('/api/me');
$request->addHeader('Authorization', 'Bearer '.$accessToken);
$response = $request->send();
echo ($response->getBody(true));die;
}
Try it by going back to the homepage and clicking "Authorize". Simply refreshing the page won't work here, as the authorization code will have already expired. With any luck, you'll see a JSON response with information about the user:
{
"id": 2,
"email": "brent@knpuniversity.com",
"firstName": "Brent",
"lastName": "Shaffer"
}
This works of course because we're sending an access token that is tied to Brent's account. This also works because when we redirect the user, we're asking for the profile
scope.
And with that, we've seen the key parts of the authorization code grant type and how to use an access token in our application. But where should we store the token and what if the user denies our application access? We'll look at these next.
Hey Spiro Ciko!
Sorry for the slow reply! For some reason, your comment ended up in the Disqus Spam :(.
I this the entire error? It looks like some sort of MySQL error, but I don't actually see the error. It's possible that some of the columns that are being set to null are required, but I'm not sure! If you have a screenshot of the error, that would be perfect :).
Cheers!
Not sure why, but I had to edit 2 files in vendor to make the server not to err for the authorization request:
server/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php:51:
protected $responseTypes; ==> protected $responseTypes = [];
server/vendor/bshaffer/oauth2-server-httpfoundation-bridge/src/OAuth2/HttpFoundationBridge/Response:57:
count ==> strlen
In both cases the author counted uncountable things...
Hi IvanPuntiy
Sorry about that! Hmm, it works for us, though we do see a warning in PHP 7.2. We're going to leave code as is for now, to see if anyone else has any issues.
Cheers!
What is the reason the scoped Symfony HTTP Client discards 'auth_bearer' options? I created scoped client with 'base_uri' 'http://coop.apps.symfonycas...'. Then added 'auth_bearer' option with `access_token` while calling '/api/me' endpoint. Some debugging shows that 'auth_bearer' options consumed by HTTP Client but Wireshark displays no 'Authorization' header in the actual request. What is the explanation of this phenomena?
Hey erop!
Your'e asking specifically about this behavior in the Symfony HTTP Client? https://symfony.com/doc/current/http_client.html
If so, I'm not sure! I'm not aware of this, nor do I see any reasons why it should be working this way. Using auth_bearer
specifically when creating a scoped token is allowed - https://symfony.com/doc/current/http_client.html#authentication - and even if you don't configure that, then pass it in when actually making the request, I can't think of any reason why that would happen. If you're pretty sure of this behavior, I'd open up an issue about it - that doesn't seem right to me.
Sorry I can't say more!
Cheers!
Is coop still be working in 2020? I am going out of my mind trying to follow this tutorial and i am doing it using the new HttpClient and I cant get it to work and I am starting to wonder if the issue is with Coop (no idea if its still maintained) because i started getting some weird 400 errors too. I have this and it keeps telling me i need an access token (but if i post to pick eggs that works fine):
` $httpClient = HttpClient::create();
$response = $httpClient->request('POST', 'http://coop.apps.knpuniversity.com/token', [
'body' => [
'client_id' => 'tsdfdsfdsf',
'client_secret' => 'secretgoeshere',
'grant_type' => 'client_credentials',
]
]);
$statusCode = $response->getStatusCode();
$responseBody = $response->toArray();
$accessToken = $responseBody['access_token'];
$expiresIn = $responseBody['expires_in'];
//var_dump($responseBody);
$response = $httpClient->request('GET', 'http://coop.apps.knpuniversity.com/api/me', [
'headers' => [
'Authorization' => 'Bearer '.$accessToken
]
]);
$statusCode = $response->getStatusCode();
$content = $response->getContent();
try {
$contentArr = $response->toArray();
var_dump($contentArr);
} catch (\Exception $e) {
//$output->writeln($e->getMessage());
}
var_dump($content);
`
I always get string(75) "{"error":"access_denied","error_description":"an access token is required"}"
I checked all the permission boxes on coop for this app to rule that out. I do get a token from the /token and am passing it with Bearer.
EDIT: i just tried with the latest guzzle and still its telling me "access token is required"
`var_dump($accessToken);
$client = new Client(['base_uri' => 'http://coop.apps.knpuniversity.com/']);
$headers = [
'Authorization' => 'Bearer ' . $accessToken,
// 'Accept' => 'application/json',
];
$response = $client->request('GET', '/api/me', [
'headers' => $headers
]);
$output->writeln($response->getStatusCode());
$output->writeln($response->getBody());`
Hey Michael L.
Can you try to change uri of COOP app to http://coop.apps.symfonycas... I guess it can work not stable due to redirect to the new domain, however it should work. If it doesn't help let me know here and we'll try to investigate it.
Cheers!
I would suggest putting a notice on the start of the tutorial that you must use the new URL so others dont go crazy like I did....
YES! it works! OMG I was tearing my hair out because for some reason the POST's all work fine with the redirect but the GET's didnt.
Hey Folks thanks for this tutorial!
I've seen the composer get broken by version incompatibility while versions climbs higher... The easiest way around this, is to go fix the version numbers.
In composer.json adjust every version according to this pattern: "1.2.3"
So remove every "~"-sign and if the the version is composed out of two parts (major and minor), then add a third part (the build) ".0" to it. So for example you get from "~1.5" to "1.5.0".
Safe the file, do 'composer update' and your good to go.
I had to add downgrade to Symfony2 by adding the following line:
`
"require": {
"symfony/symfony": "2.*",
...
`
to <b>client/composer.json</b> in order to follow this tutorial. Otherwise you'll get errors due to deprecation and removal errors.
Thanks Vladimir Z.! And yep, the content of this tutorial is holding strong... but some of the tools that we used are aging. If you use download the "start" course code, it all should work - that uses all the original versions of the libraries. For the most part, those tools are really secondary to what we're learning anyways.
But, btw, if anyone has any issues, definitely let us know :).
Cheers!
Hey Saad!
Right now, the download button doesn't show up unless you own this screencast (or have the all-access pass). I want to fix that, but for now, you can also get the starting state of the project right form GitHub: https://github.com/knpunive...
Cheers!
I'm running nginx, and have created the conf file for this 'client' site, I'm using a port rather than socket for php-fpm in nginx conf file for 'client' site i.e. 127.0.0.1:9000, and added it server name to local hosts file.
I get the home page but all the css & js not found, i.e. {{ app.request.basePath }}/css/bootstrap.css }} doesn't resolve properly.
I've done the composer install, is there anything else I'm missing?
Hey Todd,
Could you debug what is your "app.request.basePath" path is? Is it a path to web/ directory where your index.php is? Do you configure web/ folder as a document root in Nginx config? Are you sure that bootstrap.css file exists in path/to/web/css/bootstrap.css? Can you access any other files in document root except index.php? Also, could you get a page other than homepage? Does it works the same way?
Cheers!
Hey Victor,
I've tried debugging 'app.request.basePath', yes this is the cause of the problem, as it returns empty.
My document root for it's nginx config points to the web/ folder. All the bootstrap css & js files reside in the correct path.
I can navigate to other pages such as login & register etc..but css & js path's don't get resolved.
Cheers.
Hey Todd,
Good debugging! So there're 2 possible good ways I think:
- Start using asset() Twig function: https://silex.sensiolabs.or...
- Or you can cheat a bit with homepage URL, for example: "{{ path('homepage') }}/css/bootstrap.css", where homepage route always is a correct address of the web/ folder, so you just need to add the path to assets *relative* to the web/.
Cheers!
If you are getting a pdodriver not found exception you may have to install sqlite and php5-sqlite. I am running on vagrant box ubuntu 14.04.
Here are the commands if you are ubuntu flavored linux
sudo apt-get install sqlite
sudo apt-get install php5-sqlite
In <strong>server/composer.json</strong> I see the following two components<br />"require": {<br />...<br />"bshaffer/oauth2-server-php": "dev-develop",<br />"bshaffer/oauth2-server-httpfoundation-bridge": "dev-master",<br />...<br />}<br />
I need to implement an OAuth2 server. Would you recommend to use these libraries for my purposes, or are there better alternatives. I'm not done with this course and haven't done much research yet, that's why I wanted to ask your suggestion.
Thanks,
Hey Vlad,
Yes, you can use it for sure - they are pretty stable now. Btw, those libs are from a guy who works at Google ;) There's another one cool lib I could advice you: https://github.com/thephple... . However, if you want to match our screencasts - bshaffer's libs are a good choice I think.
Cheers!
In the "Authorization Code Grant Type" video, where we are "Exchanging the Authorization Code for an Access Token",
I can't get the $responseBody to var_dump in the receiveAuthorizationCode() function (copied below). Instead of the expected $responseBody, I see the "Hello World" string that my redirect_uri file /web/handle.php prints out.
Because I couldn't get the following code to work on my test site:
$redirectUrl = $this->generateUrl('coop_authorize_redirect', array(), true);
I hard-coded my redirect_uri like so:
public function redirectToAuthorization(Request $request)
{
$url = 'http://coop.apps.knpuniversity.com/authorize?'.http_build_query(array(
'response_type' => 'code',
'client_id' => 'Debra\'s Other Test App',
'redirect_uri' => 'http://localhost:9000/handle.php',
'scope' => 'eggs-count profile'
));
return $this->redirect($url);
Could that be why my receiveAuthorizationCode() isn't working?
And, in any case, how could the receiveAuthorizationCode() even run if COOP is redirecting to /web/handle.php and the function that needs to fire is inside /src/OAuth2Demo/Client/Controllers/CoopOAuthController.php?
What have I done wrong?
Also, I'm wondering if I need to replace this line with more standard PHP since the course says that that function is customized for the tutorial's app. (although I'm not sure what standard PHP redirect code is).
return $this->redirect($url);
Thanks in advance for your help.
public function receiveAuthorizationCode(Application $app, Request $request)
{
// equivalent to $_GET['code']
$code = $request->get('code');
$http = new Client('http://coop.apps.knpuniversity.com', array(
'request.options' => array(
'exceptions' => false,
)
));
/* 1. Get the Access Token */
$request = $http->post('/token', null, array(
'client_id' => 'Debra\'s Other Test App',
'client_secret' => '9713c486bdab888ee840b9ee31962946',
'grant_type' => 'authorization_code',
));
// make a request to the token url
$response = $request->send();
$responseBody = $response->getBody(true);
var_dump($responseBody);die;
}
Hey Debra,
First of all, do you use Silex framework as we do in this screencast? If no, then generateUrl() won't work until you create it. But if you use Silex as we, let's fix the problem with this generateUrl(), but I need to know what exactly error do you have with it to help you fix it.
About hard-coded the redirect_uri - no problem, I don't think the problem in it, of course, if you hard-coded the correct URL ;)
<blockquote>
And, in any case, how could the receiveAuthorizationCode() even run if COOP is redirecting to /web/handle.php and the function that needs to fire is inside /src/OAuth2Demo/Client/Controllers/CoopOAuthController.php?
</blockquote>
That's a good question. In shorts, you need a front-controller, an entry point for every request to your application - it's web/index.php file in out case. Then with router system you point a route (a specific URL) to a specific controller, e.g. you set up "receiveAuthorizationCode()" to match the "/coop/oauth/handle" route, so when you go to the"/coop/oauth/handle" URL - the receiveAuthorizationCode() method will be fired up. We configure these routes in "CoopOAuthController::addRotues()". But if you're not using Silex, it's almost pointless for you - you need to implement this in your application somehow. Actually, it's basics of any framework.
What about replacing redirect() with a more standard PHP redirect code, it depends on what framework do you use. If you're on Silex, then you better use this redirect() built-in function. But if you're write withut any framework, of course this redirect() won't work since most probably you don't have this function. Then you need to use native PHP support to make redirects:
header('Location: http://www.example.com/');
exit;
But keep in mind it has some conditions, see header() PHP built-in function for more information: http://php.net/manual/en/function.header.php
Cheers!
Question on the course payment ($12). Is that for the entire OAuth course, the complete 8 steps, or just this step? Thanks.
Hey Ramon,
The "OAuth2 in 8 Steps" is just a name! :) So $12 is a price of the whole course, which contains of 11 chapters.
Cheers!
Thank you for the tutorial. I've been staring at the RPC spec docs for 3 weeks and my eyes were sore.
Near the end of video "Authorization Code Grant Type", I first receive the access token and then use that access token to connect to the /api/me endpoint with an "Authorization" header. I get the proper json response including my Coop user_id and then try to save to my local db which fails. I've stepped through the code with Xdebug to verify this and it appears to try and create a new user in the sqlite db instead of updating. My code is below. Thank you for your help:
/coop/start/client/src/OAuth2Demo/Client/Controllers/CoopOAuthController.php
public function receiveAuthorizationCode(Application $app, Request $request)
// ...
$response = $request->send();
$responseBody = $response->getBody(true);
$responseArr = json_decode($responseBody, true);
$accessToken = $responseArr['access_token'];
$expiresIn = $responseArr['expires_in'];
$expiresAt = new \DateTime('+' . $expiresIn . ' seconds');
$request = $http->get('/api/me');
$request->addHeader('Authorization', 'Bearer ' . $accessToken);
$response = $request->send();
$json = json_decode($response->getBody(), true);
$user = $this->getLoggedInUser();
$this->coopAccessToken = $accessToken;
$this->coopUserId = $json['id'];
$this->coopAccessExpiresAt = $expiresAt;
if ($this->saveUser($user)) // This part fails and $json['id'] is not saved
{
echo $response->getBody(); die;
}
die('Save failed');
I figured out what I was doing wrong. I was using $this instead of $user when setting the coopAccessToken, coopUserId, and coopAccessExpiresAt properties. Changing the end of my code to the following worked:
/..
$user = $this->getLoggedInUser();
$user->coopAccessToken = $accessToken;
$user->coopUserId = $json['id'];
$user->coopAccessExpiresAt = $expiresAt;
if ($this->saveUser($user)) // This part fails and $json['id'] is not saved
{
echo $response->getBody(); die;
}
die('Save failed');
Hey Nathan! I'm glad you got it sorted out - I was just looking through your first comment when I saw you had already fixed it. Awesome! Good luck!
in /home/spyros/work/p3/start/client/src/OAuth2Demo/Client/Storage/Connection.php line 79
at PDOStatement->execute(array('email' => 'spyrostsiko@punct.ro', 'password' => '9P6NWcis9oBCRdfgZgu/ARRAmP6929RHZbvAxRiUNpThyEJny9kR57O2VDDRKufCGPuNLPoUeE2g+tbIcQBbGg==', 'firstName' => 'Spyors', 'lastName' => 'Tsiko', 'coopUserId' => null, 'coopAccessToken' => null, 'coopAccessExpiresAt' => '', 'coopRefreshToken' => null, 'facebookUserId' => null)) in /home/spyros/work/p3/start/client/src/OAuth2Demo/Client/Storage/Connection.php line 79
at Connection->saveUser(object(User), true) in /home/spyros/work/p3/start/client/src/OAuth2Demo/Client/Storage/Connection.php line 99
at Connection->createUser('spyrostsiko@punct.ro', '9P6NWcis9oBCRdfgZgu/ARRAmP6929RHZbvAxRiUNpThyEJny9kR57O2VDDRKufCGPuNLPoUeE2g+tbIcQBbGg==', 'Spyors', 'Tsiko') in /home/spyros/work/p3/start/client/src/OAuth2Demo/Client/Controllers/UserManagement.php line 58
at UserManagement->registerHandle(object(Application), object(Request))
at call_user_func_array(array(object(UserManagement), 'registerHandle'), array(object(Application), object(Request))) in /home/spyros/work/p3/start/client/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php line 126
at HttpKernel->handleRaw(object(Request), '1') in /home/spyros/work/p3/start/client/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php line 66
at HttpKernel->handle(object(Request), '1', true) in /home/spyros/work/p3/start/client/vendor/silex/silex/src/Silex/Application.php line 516
at Application->handle(object(Request)) in /home/spyros/work/p3/start/client/vendor/silex/silex/src/Silex/Application.php line 493
at Application->run(object(Request)) in /home/spyros/work/p3/start/client/web/index.php line 27
i got this error why this happend when i register ?
I didnt change anything from that script.