> APIs >

Course Overview

Login to bookmark this course

Symfony RESTful API: Authentication with JWT (Course 4)

Discover Symfony's API security with JWT. Master creating, signing & returning JWT's and effective error handling.

  • 2874 students
  • EN Captions
  • EN Script
  • Certificate of Completion

Your Guides

About this course

This tutorial uses an older version of Symfony. The concepts of API tokens & JWT are still valid, but integration in newer Symfony versions may be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.0.*", // v3.0.3
        "doctrine/orm": "^2.5", // v2.5.4
        "doctrine/doctrine-bundle": "^1.6", // 1.6.2
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // v2.10.0
        "sensio/distribution-bundle": "^5.0", // v5.0.4
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.14
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.2
        "jms/serializer-bundle": "^1.1.0", // 1.1.0
        "white-october/pagerfanta-bundle": "^1.0", // v1.0.5
        "lexik/jwt-authentication-bundle": "^1.4" // v1.4.3
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.6
        "symfony/phpunit-bridge": "^3.0", // v3.0.3
        "behat/behat": "~3.1@dev", // dev-master
        "behat/mink-extension": "~2.2.0", // v2.2
        "behat/mink-goutte-driver": "~1.2.0", // v1.2.1
        "behat/mink-selenium2-driver": "~1.3.0", // v1.3.1
        "phpunit/phpunit": "~4.6.0", // 4.6.10
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}

After 3 tutorials, we've got a nice API, But we've been completely ignoring authentication. What about API tokens? Or properly handling errors? Thanks to some modern tools, this will be such a treat:

  • Understanding JSON web tokens (JWT)
  • Creating, signing & returning JWT's
  • Using Guard for a custom JWT authentication system
  • Sending tokens on the Authorization header
  • Proper API errors for invalid credentials and missing credentials
  • Choosing to split into 2 firewalls

Next courses in the APIs: REST in Symfony 2 & 3 section of the APIs Track!

49 Comments

Sort By
Login or Register to join the conversation
Default user avatar Marius Puiu 6 years ago

Daily F5 press :D

6 | Reply |
Tt S. avatar Tt S. 6 years ago

Wow!!!!! It's being so long~ Thanks for the updates!

1 | Reply |
Default user avatar fredrsf 6 years ago

Hello, how I can to make logout action with JWT? I remove token from localStorage object, but PHPSESSID exists.

1 | Reply |

Hi fredrsf!

So, "logging out" with JWT can be tricky. The reason is that - in a true API - your authentication is "stateless" - i.e. there is no "logout" - either you send a valid token in the request to authenticate, or you don't, and you're not authenticated :). If you had an iPhone app that used your API and you wanted to "logout", this could be as simple as making the iPhone app "forget" the token, so that authenticated requests can no longer be made.

But, there are two problems. By design, JWT's are not meant to be invalidated. So, even if you make your iPhone app (or JavaScript) "forget" the JWT, it's still technically valid until it expires. If you want to actually *invalidate* the JWT, you need to do a bit more work. Here's an interesting SO about this: http://stackoverflow.com/qu....

The second problem is what you're talking about: it sounds like you're using JWT from JavaScript. And it also sounds like your firewall is "stateful" (the default setting), which means that your session is being stored in a cookie. In this setup, you're AJAX calls are authenticated for *two* reasons: (A) because you're sending a JWT and (B) because after logging in once with your JWT, Symfony "remembers" that you're logged in via a cookie.

There are two ways to fix this. Since you're using JWT to authenticate, the best solution is to use a separate firewall for your API endpoints and set "stateless: true" on that firewall, so that nothing is stored in the session/cookie. This makes your API authentication stateless, as it should be.

Otherwise, when you "logout", you'll need to either hit the "/logout" endpoint (e.g. via AJAX) or hit some endpoint and programmatically logout the user (http://stackoverflow.com/qu....

Let me know if this helps! Often, if I'm making an API *only* so that I can consume it via *my* JavaScript, I won't bother with JWT, and I'll just allow the user to login normally and use sessions. That's another option. But using sessions *and* JWT can making logging out a little weird :)

Cheers!

3 | Reply |
Default user avatar fredrsf weaverryan 6 years ago edited

Hi weaverryan ! I build some SPA application with rest api (symfony) + reactjs. This thoughts appeared after I saw this slides http://www.slideshare.net/w.... Thank your team! And now your answer very helped me, I set stateless on true and used your links for invalidate jwt keys. Thank your!!!

| Reply |

Sweeet! Great job! :)

| Reply |
Abelardo avatar Abelardo 2 years ago

Hi there,

Once you have logged in, what do you do with your jwt token in the front end: save into cookies, send with your post, get, requests? How would you save the jwt token in frontend to be used as many times as you want: cookies, etc.?

Best regards.

| Reply |

Hey Abelardo L.!

Answered over here for you :) https://symfonycasts.com/sc...

1 | Reply |
picks avatar picks 5 years ago edited

Hi there, I'm getting more and more deprecated warnings from the ApiTestCase::setUpBeforeClass function with phpunit, and I can't find how to correct them before I don't see any related link between the deprecation and the function( at least in the code). Could you please help me? Here are the warnings I get :

`Remaining deprecation notices (5)

2x: Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead for "Psr\Log\LoggerInterface".

2x in ApiTestCase::setUpBeforeClass from MyBundle\Test

1x: The "Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\DefaultJWSProvider" class is deprecated since version 2.5 and will be removed in 3.0. Use "Lexik\Bundle\JWTAuthenticati
1x: The "Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\DefaultJWSProvider" class is deprecated s
ince version 2.5 and will be removed in 3.0. Use "Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\Lc
obucciJWSProvider" or create your own "Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderIn
terface" implementation instead.

1x in ApiTestCase::setUpBeforeClass from MyBundle\Test

1x: The Symfony\Component\ClassLoader\ClassLoader class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Composer instead.

1x in ApiTestCase::setUpBeforeClass from MyBundle\Test

1x: Using the KERNEL_DIR environment variable or the automatic guessing based on the phpunit.xml / phpunit.xml.dist file location is deprecated since Symfony 3.4. Set the KERNEL_CLASS environment variable to the fully-qualified class name of your Kernel instead.
Not setting the KERNEL_CLASS environment variable will throw an exception on 4.0 unless you override the :createKernel() or :getKernelClass() method.

1x in ApiTestCase::setUpBeforeClass from MyBundle\Test`
| Reply |

Hey picks

Try passing the "verbose" flag when running phpunit so you can get more debug information


php ./vendor/bin/phpunit -vvv

Cheers!

| Reply |

Hi, this did not get me more details I'm afraid...

| Reply |

Oh sorry, my fault, you have to pass in the "--debug" flag. Anyway, for each deprecation you will have to find the related change. For some of those there are blog posts, like the first one, I already found it for you: https://symfony.com/blog/ne...
or you can check the UPGRADE or CHANGELOG file at the Github repository: https://github.com/symfony/...
(you may have to change the branch so you can see older CHANGELOG's)

| Reply |
Bartlomeij avatar Bartlomeij 5 years ago

Hello guys,
I'm creating a new API with Symfony 3.4, but after this course I still got 2 tests failed. Other tests have been passed. That looks kinda weird for me.
After debugging it looks like is never visiting onAuthenticationFailure() and start() methods, but is throwing BadCredentialsException. Please help!

1. testBadToken: Failed asserting that 500 matches expected 401.
Content-Type: application/json
{
"error": {
"code": 500,
"message": "Internal Server Error",
"exception": [
{
"message": "Invalid JWT Token",
"class": "Lexik\\Bundle\\JWTAuthenticationBundle\\Exception\\JWTDecodeFailureException".....

2. testPostCreateInvalidTokenCredentials: Property "detail": Expected "Invalid Credentials." but response was "''"
Content-Type: application/problem+json
{
"detail": "",
"status": 401,
"type": "about:blank",
"title": "Unauthorized"
}

| Reply |

Hey Bartlomeij!

Hmmm. Let's see what we can figure out :).

  1. testBadToken: Failed asserting that 500 matches expected 401.

So, for this one, you're getting a JWTDecodeFailureException. So, it's not that the BadCredentialsException is being thrown, but during the guard process, we're asking JWT to decode the token, and that is failing. Of course the question is WHY. It could be that we are sending or reading the token wrong in your authenticator - I would do some debugging and dump the JWT right before you try to decode it and make sure it looks exactly like it should.

  1. testPostCreateInvalidTokenCredentials: Property "detail": Expected "Invalid Credentials." but response was "''"

In this case, you ARE getting the correct application/problem+json format and data. It's just that you're not getting the right data on the "details" key. You need to track down where this response is coming from. If your code matches the finish code for this tutorial, then there are only 3 possibilities:

  • JwtTokenAuthenticator::onAuthenticationFailure()
  • JwtTokenAuthenticator::start()
  • ApiExceptionSubscriber

You said the first two aren't being hit. I would triple-check that ;). If the third one is being hit, then here's what I would do: temporarily go into that class and inside onKernelException(), put these 2 lines first:


$e = $event->getException();
throw $e;

This will re-throw the exception instead of just returning a response without too many details. It will allow you to see what the real exception is (you mentioned it might be BadCredentialsException) and more importantly, where the exception is coming from. If it is a BadCredentialsException, then something isn't right. Because, if you throw a BadCredentialsException from inside a Guard authenticator, that WILL trigger onAuthenticationError().

Good luck!

| Reply |
Bartlomeij avatar Bartlomeij weaverryan 5 years ago edited

Hey weaverryan , thank you for reply.

> 1. testBadToken: Failed asserting that 500 matches expected 401.

Okay, I fixed that. Just had to change:

$data = $this->jwtEncoder->decode($credentials);
To:
try {
$data = $this-> jwtEncoder->decode($credentials);
} catch (JWTDecodeFailureException $e) {
$data = false;
}

Because a decode method is not returning false when invalid token, but throwing JWTDecodeFailureException.

> 2. testPostCreateInvalidTokenCredentials: Property "detail": Expected "Invalid Credentials." but response was "''"

This response is coming from ApiExceptionSubscriber:onKernelException(), but Exception has a empty message. Trace:
#0 ../vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php(109): Symfony\Component\Security\Http\Firewall\ExceptionListener->startAuthentication(Object(Symfony\Component\HttpFoundation\Request), Object(Symfony\Component\Security\Core\Exception\BadCredentialsException))

So it looks like BadCredentialsException is beeing thrown is newTokenAction, but in onKernelException I got a HttpException. Any ideas why?
Thanks!

| Reply |

Hey Bartlomeij!

1) That makes perfect sense - nice job!

2) Ohhh! The key part is:

BadCredentialsException is being thrown is newTokenAction

So this is testing the endpoint where the user posts their credentials to receive a JWT - I should have looked closer at the test method name in your original post! But, to be honest with you, I can't figure out how this worked before. I mean, I know it did... but this tutorial is a few years old now, and I can't "connect the dots".

Let me explain - and I'll tell you the fix (even if I can't see why it worked before, but not now). When throw the BadCredentialsException in newTokenAction, eventually, the ApiExceptionSubscriber class is called that we created. THIS class is responsible for handling that exception and turning it into a JSON response. but you already knew this much :). Here is the important part (and the part that I can't fully explain): when you throw an AuthenticationException of any type (the BadCredentialsException is a sub-class of this), the actual message that you're supposed to use is NOT stored in the "message" property and accessible via getMessage(). Nope, it's stored in a getMessageKey() method. There are security reasons for this. I can't explain how this ever worked, but the result you're seeing makes sense: the BadCredentialsException has no "message" on it, so it makes sense that the details would be blank. The logic we seem to be missing is this:


if ($e instanceof HttpExceptionInterface) {
    $apiProblem->set('detail', $e->getMessage());
-}
+} elseif ($e instanceof AuthenticationException) {
+    $apiProblem->set('detail', $e->getMessageKey());
+}

Try that out and let me know. It's a mystery how it worked before, but I think this should get it working :).

Cheers!

| Reply |
Bartlomeij avatar Bartlomeij weaverryan 5 years ago edited

The solution doesn't work but this is an interesting part.
If I try to dump something just before throwing new BadCredentialsException I can see that in my response:
`
if (!$isValid) {

var_dump('foo');
throw new BadCredentialsException();

}
<br />That means BadCredentialsException is throwing. But when I try to get class name in ApiExceptionSubscriber:<br />
var_dump(get_class($e));
`
I got..... Surprise! "Symfony\Component\HttpKernel\Exception\HttpException"

Got a one more interesting question - @UniqueEntity in my entity class seems not working properly. Trying to save another entity with the same email address I got an 500 error "An exception occurred while executing 'INSERT INTO... Integrity constraint violation: 1062 Duplicate entry'.
Is throwing UniqueConstraintViolationException but I'm looking for a global solution. I'd like to have a way to protect my code with error 'This email address already exists' like in the User Authorization course - for the API it doesn't work :( I don't know why is returning true on $form->isValid method. It shouldn't I guess.

| Reply |

Hey Bartlomeij!

I got..... Surprise! "Symfony\Component\HttpKernel\Exception\HttpException"

Woh... that IS a surprise! Hmmm. So something is converting the exception from a BadCredentialsException to an HttpException before our subscriber. I'm really not sure what would be doing that - but it MUST be a listener on kernel.exception (like our subscriber). Try running this to see if there are any other event listeners on that kernel.exception that look out-of-the-ordinary (you could even do some debugging in these if you want - a listener change the exception by doing something like this https://github.com/symfony/symfony/blob/5c7390006b650ed847ebd7780a2fd18ce9163ad0/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php#L118)


php bin/console debug:event kernel.exception

You only need to look at the event listeners before our subscriber - any listeners after are executed after our subscriber.

Got a one more interesting question - @UniqueEntity in my entity class seems not working properly

That also doesn't make any sense :/. This should "just work" - if you have that annotation your entity, when you bind the form, validation is automatically executed and the $form-=>isValid() will return false. So, I think you'll need to do a bit of digging. I would open up the core UniqueEntityValidator class (https://github.com/symfony/symfony/blob/5c7390006b650ed847ebd7780a2fd18ce9163ad0/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php#L43) and make sure it's being executed. I would then put debug code in there to figure out why it's ultimately returning false when it should be returning true.

Sorry I can't help more - those are both very strange issues - so we need to do some digging!

Cheers!

| Reply |

>You only need to look at the event listeners before our subscriber - any listeners after are executed after our subscriber.

Everything looks properly. Registered Listeners for "kernel.exception" Event:
#1 WhiteOctober\PagerfantaBundle\EventListener\ConvertNotValidMaxPerPageToNotFoundListener::onException() 512
#2 WhiteOctober\PagerfantaBundle\EventListener\ConvertNotValidCurrentPageToNotFoundListener::onException() 512
#3 AppBundle\EventListener\ApiExceptionSubscriber::onKernelException() 0
#4 Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException() 0
#5 Symfony\Bundle\SwiftmailerBundle\EventListener\EmailSenderListener::onException() 0
#6 Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelException() -64
#7 Symfony\Component\HttpKernel\EventListener\ExceptionListener::onKernelException() -128

> Got a one more interesting question - @UniqueEntity in my entity class seems not working properly
Okaaay, My bad. I know why that is not working. I'm validatiing Dto object before I'll create a MySql model. :) Forget that.

| Reply |

Yea, there are only 2 listeners before our subscriber - and neither of those are causing it. I'm honestly not sure - it simply doesn't make sense. When we throw an exception in our controller, that is immediately caught by Symfony and the kernel.exception event is dispatched. The exception that is on the event in our event subscriber *should* be that same exception - there are no other layers in between. Would it be possible for you to push a repo up to GitHub? I could definitely track it down then :).

Cheers!

| Reply |

I wrote to you on your email ;) Please confirm you received the email. Thanks!

| Reply |

Hey Bartlomeij!

I'm not sure I did. Did you use ryan@symfonycasts.com or hello@symfonycasts.com? Either of those are best :).

Cheers!

| Reply |

I used ryan@thatsquality.com, I found that mail on your github. But okay, I've already used rean@symfonycast.com to contact you. Please confirm ;)

| Reply |

got it!

| Reply |

Hey weaverryan!

Ok, so I've got the issue! I was a little mistaken on how it was *supposed* to work - it's been awhile since I recorded this episode :). Here's how it's *supposed* to work:

1) You throw the BadCredentialsException in your controller

2) Because this is an instance of Symfony AuthenticationException, the ExceptionListener from Symfony's security system sees this and understands it to be some sort of authentication issue. To "invite" you to authenticate it, it calls your firewall's "entry point".

3) With Guard, the entry point is the start() method on your authentication. THAT is what should ultimately convert this into the API Problem response format.

So, what's different with your situation? You have no entry point :). And so, Symfony doesn't know what to do, and converts the Exception into an HttpException with a 401 status code. Why do you not have an entry point? Well, each request into your app activates exactly *one* of your firewalls. And each firewall has 0 or 1 entry points (again, if you're using Guard, then the start() method on 1 of your authenticators will be your entry point). However, when you POST to your token endpoint, the URL activates your "main" firewall. This firewall has no authenticator and thus no entry point. And so, you get the error. Your athletes firewall *does* have an authenticator + entry point, but that is not the currently-active firewall during this request.

Each firewall is like an independent security system, and not counting the fake "dev" firewall, I usually only recommend having one, though having 1 for your frontend and 1 for your admin can also make sense depending on your situation. But, you just need to keep this in mind: each firewall is an independent security system: so if you try to authenticate against one of them, you will not be authenticate against the others and their authenticators or entry points won't apply.

I hope that helps solve the mystery! It definitely took me a bit of digging - this stuff is hard ;).

Cheers!

| Reply |
Bartlomeij avatar Bartlomeij weaverryan 5 years ago edited

Hey weaverryan !
You are awesome like always, thank you very much!

| Reply |

Any tips or hints on implementing refresh tokens in a secure manner?

| Reply |
Zorpen avatar Zorpen 6 years ago edited

Hi there
Thanks for awesome course!
Let's say we have POST /api/comments endpoint where api consumers can post some comments.
For simplicity sake let's assume than comment contains only user_id and message fields, so posting:
{ user_id: 2, message: "Some comment" } creates comment for user with ID 2.
Now, we have 2 types of api consumers. "Admins" can do "everything", when normal users can use our api only in their own user behalf.
So my question is - how can we "block" normal users from creating comments for other users? Some check should be in controller action where in case of admin posted user_id is taken into consideration and stored in db, when in case of normal user user_id is always taken from $this->getUser(), and posted field is ignored?
Best regards!

| Reply |

Hey Zorpen

Good news! you can achieve that very easily by using Voters, and you know what, we have a quick and elegante tutorial about it https://knpuniversity.com/s...
or if you prefer reading the docs, I left you this link: http://symfony.com/doc/curr...

Cheers!

| Reply |
Zorpen avatar Zorpen MolloKhan 6 years ago edited

Thanks MolloKhan
I know about voters, but like I described (maybe poorly) I don't want to deny access to /api/comments for normal user, I want them to post comments but only as them.
Right now any user can post {user_id: 10, message: "some comment"} to post a comment as user 10 even if their user id is for example 5.
What I want to achieve is, for admin to create comments as anyone, and normal users to post comments only as them, and I don't know if controller action is a good place to check api consumer role and in case of admin add comment with posted data, and in case of normal user add comment with user_id taken from $this->getUser()->getId() even if they posted another user_id in request body.

| Reply |

Hey Zorpen! (Sorry for the late response)

I got you now, I misunderstood you ;)

Ok, I have an idea, in your controller, where you render the CommentForm, check if user has role "Admin" (I supose you already know how to do that) and pass that value into your FormType (there are a couple of ways to pass values into a FormType, if you don't know how just ask or check symfony docs), then if it's true (user has admin role) render user field (so he can choose an user), and if not, just dont render it, you will have to check it too in your Form template, and finally, when the form is submitted, you will have to do what you say, if user hasn't admin role, then you grab his ID ($this->getUser()->getId())

Cheers!

| Reply |
Zorpen avatar Zorpen MolloKhan 6 years ago edited

Thanks MolloKhan
Have a nice day :)

| Reply |
Default user avatar Murilo Lobato 6 years ago

Waiting hard for this episode :0

| Reply |

Hey Murilo!

In the mean time - check out http://symfony.com/doc/curr.... You may need to setup an OAuth server (there is a bundle for this - FOSOAuthServer), or you might just need a very simple token-based authentication system. We also setup the latter in our REST Silex tutorial - it might help too http://knpuniversity.com/sc.... The Silex tutorial uses a "harder" version of Symfony authentication than the above-linked Symfony docs page (because the simpler way isn't/wasn't supported in Silex), but you might be able to put the ideas together.

I'll also be releasing a library (should be next week) called Guard that will drastically simplify setting up a custom authentication situation in Symfony. But, The Symfony link above isn't too hard either.

I hope this helps! This tutorial is probably 2 months away... and I figured you probably can't wait that long ;).

Cheers!

1 | Reply |
Default user avatar Murilo Lobato weaverryan 6 years ago edited

Hey thank you for the reply, actually a month ago I was researching for way to implement it now. And, even after those links helped me a lot, I still don't understand a few things.

I already have FOSUserBundle installed, and some CRUDs all prefixed by a '/admin' slug. So, my firewall is called "secured_area" is configured accordingly to FOSUserBundle docs.

My doubts:

1) Should I have another firewall, called api_secured_area configured like following:


    api_secured_area:
        pattern: ^/api
        stateless: true
        simple_preauth:
            authenticator: apikey_authenticator
        provider: api_key_user_provider

or I just need to add the “simple_preauth” part to my existing firewall?

2) Is correct that my “api_key_user_provider” extends the FOS\UserBundle\Security\EmailUserProvider to reuse and centralize the provider logic?

And now, the worst part at least for me (:

3) For my mobile apps be able to create a token, I will need to have a "/api/tokens" route (i guess), with HTTP_BASIC auth. The question is: Is correct to have another firewall to define this route as http_basic?

| Reply |

Hey again!

If it helps, Guard *is* available now, and would work fine with FOSUserBundle (you could even let FOSUserBundle do the form login stuff, then use Guard only for the API token auth): https://knpuniversity.com/s...

Here are some answers assuming you *don't* change to Guard:

1) I would probably use 1 firewall for this, though you could probably also have 2. With 1 firewall, you're basically saying "I really have 1 security / user system, but I want to make it possible for someone to "login as a user" via an API key"

2) This part gets more complex. Here's the easiest answer - it's a bit of a "cheat", but will work brilliantly. Don't override or change the FOSUserBundle's user provider at all. So, don't have a custom "provider" key that points to some "api_key_user_provider" - just use the normal FOSUserBundle stuff. Inside your api key authenticator, in authenticateToken(), you won't be able to use the $userProvider argument, because this will be from FOSUserBundle and won't have any custom methods that you need. But that's ok. Instead, inject the EntityManager into your authenticator, lookup your User repository, and query for the user based on the token (your exact logic here may differ). I recommend something very similar to this in Guard in its getUser() method - https://knpuniversity.com/s....

3) You're right that you will probably have some endpoint like /api/tokens where you will POST with HTTP BASIC authentication in order to be able to create a token. But now, you should not have another firewall for this - just add the http_basic stuff under your existing firewall. You will now have 1 firewall with *3* different ways to authentication (form_login, simple_preauth and http_basic). That's perfect: those are just three different ways to help a user login to his/her *one* account.

Cheers!

| Reply |
Default user avatar Tahmina Khatoon 6 years ago

October is running out, I am also waiting for this tutorial.

| Reply |

Hey! We'll get this released as soon as possible, but it won't be until at least November! Until then, check out https://knpuniversity.com/s... and https://knpuniversity.com/s....

Cheers!

| Reply |
Default user avatar Tahmina Khatoon weaverryan 6 years ago

Next approximate date please, I am just waiting for this one from last year ...

| Reply |

This week :)

| Reply |
Default user avatar Tahmina Khatoon weaverryan 6 years ago

Awesome, finally it has started to publish.

| Reply |
Default user avatar Rasel Khan 6 years ago

Hey , When i'll got this tuts, I need urgent !!

| Reply |
Default user avatar Rafael 6 years ago

Any updates?!

| Reply |

Episode 3 is in the final stages of editing right now. I have not yet started on this one - so it's likely still 1 month away or longer. You can click at the top of the page and we'll message you when it's ready. We'll do our best to make it sooner! And due to some authentication changes in Symfony (Guard), this will be an *awesome* tutorial :).

Cheers!

1 | Reply |
Default user avatar Bogdan Grbatinić 6 years ago

Hi everyone,

I followed this course and decided to adapt it to symfony 3. I just finished 3rd part of the course and I'll be updating it as I go, but I decided to share it if anyone wants to have symfony 3 version. I decided to drop JMS serializer in favor of Symfony serializer component, and I'm using new version of Guzzle. I tried to make the API work the same way as in screencast, regardless of updated dependencies.

https://github.com/grbatini...

| Reply |
Default user avatar Vincent Wong 6 years ago

Awesome course, I enjoyed them so much, any update on the release schedule for course 5?

| Reply |

Thanks Vincent - really happy you enjoyed them! Course 5 is scheduled for May (this month) - it's in the final editing stages (we were aiming for next week, but might not be until the week after).

| Reply |
Default user avatar Manuel 6 years ago

oh my god, i just joined the knp university because of this tutorial...
But its not there... what do you think how much time it takes until release?

| Reply |

Hey Manuel!

Ah, bummer! A few things:

1) You can email me about the subscription if you want to pause it or cancel it until this is ready - ryan@knpuniversity.com :).

2) This tutorial will be released in September at the earliest, but more likely October.

3) Until then, I highly recommend checking out the new Guard library and tutorial we released: https://knpuniversity.com/s... - with any luck, it'll make adding authentication to your API really easy.

4) You can also check out the Silex tutorial for all this authentication stuff - there are several chapters on it here: https://knpuniversity.com/s...

Cheers!

| Reply |

Delete comment?

Share this comment

astronaut with balloons in space

"Houston: no signs of life"
Start the conversation!