Chapters
37 Chapters
|
3:43:31
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^3.0", // v3.1.2
"doctrine/annotations": "^2.0", // 2.0.1
"doctrine/doctrine-bundle": "^2.8", // 2.8.3
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.14", // 2.14.1
"nelmio/cors-bundle": "^2.2", // 2.2.0
"nesbot/carbon": "^2.64", // 2.66.0
"phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
"phpstan/phpdoc-parser": "^1.15", // 1.16.1
"symfony/asset": "6.2.*", // v6.2.5
"symfony/console": "6.2.*", // v6.2.5
"symfony/dotenv": "6.2.*", // v6.2.5
"symfony/expression-language": "6.2.*", // v6.2.5
"symfony/flex": "^2", // v2.2.4
"symfony/framework-bundle": "6.2.*", // v6.2.5
"symfony/property-access": "6.2.*", // v6.2.5
"symfony/property-info": "6.2.*", // v6.2.5
"symfony/runtime": "6.2.*", // v6.2.5
"symfony/security-bundle": "6.2.*", // v6.2.6
"symfony/serializer": "6.2.*", // v6.2.5
"symfony/twig-bundle": "6.2.*", // v6.2.5
"symfony/ux-react": "^2.6", // v2.7.1
"symfony/ux-vue": "^2.7", // v2.7.1
"symfony/validator": "6.2.*", // v6.2.5
"symfony/webpack-encore-bundle": "^1.16", // v1.16.1
"symfony/yaml": "6.2.*" // v6.2.5
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"mtdowling/jmespath.php": "^2.6", // 2.6.1
"phpunit/phpunit": "^9.5", // 9.6.3
"symfony/browser-kit": "6.2.*", // v6.2.5
"symfony/css-selector": "6.2.*", // v6.2.5
"symfony/debug-bundle": "6.2.*", // v6.2.5
"symfony/maker-bundle": "^1.48", // v1.48.0
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/phpunit-bridge": "^6.2", // v6.2.5
"symfony/stopwatch": "6.2.*", // v6.2.5
"symfony/web-profiler-bundle": "6.2.*", // v6.2.5
"zenstruck/browser": "^1.2", // v1.2.0
"zenstruck/foundry": "^1.26" // v1.28.0
}
}
12 Comments
I understand that one can always argue that things here are meant as examples. But I suppose that people actually implement things as you show here. The problem is that this way of storing API tokens is basically the same as storing passwords in clear text.
While this is an easy solution for demonstration, I strongly suggest to not do it this way on a production system.
It would be better to generate an access token consisting of two parts:
You would then need to store that hashed secret in the token entity as well of course.
For example, connect the parts with a dot so that it is easy to parse the token.
You can then fetch the token from the database using the identifier and do a „password verification“ using the secret.
I think it is not too complicated to add this. But way more secure.
Of course you need to tell the user that they must immediately save the token somewhere because you can not show it to the user again for security (and because you don’t know the secret in clear text anymore). But I saw this already on other services. So it should be fine.
Or maybe use something like a url signer (which is probably faster).
But however.. do not save API tokens in clear text.
Hey @MatthiasN!
I think this is really good advice. My intention was to show people that "you can just store tokens in the database". And while that's true (just like how you can "store passwords in a database"), in both cases it shouldn't mean storing in plaintext. So yes, this is definitely a better idea.
Anyway, thank you for posting this - good advice! I'll also add a note to the tutorial about it.
Oh, and let me add some more technical details here to help people, building on what you said:
So, for example, you'd generate 2 random strings, separated by a
.. This full string would be the API key that you show to the user.But then, you store the first part (the "locator" or "identifier") in the database on a
locatorkey ofApiToken. Then you store the second part (thesecret) on another property ofApiToken- but HASH it first. For example, here is how we generate the random locator and hash the secret in the ResetPasswordBundle - https://github.com/SymfonyCasts/reset-password-bundle/blob/main/src/Generator/ResetPasswordTokenGenerator.php#L52-L65Then, when verify if a token is valid, you split the token on
.to get the first and second part. You then query for theApiTokenvia the first part (thelocator). If found, you has the 2nd part that was just sent you to you and see if it equals the hashedsecreton theApiTokenyou just found usinghash_equals- e.g. https://github.com/SymfonyCasts/reset-password-bundle/blob/3e0bb051a514459a3c63a5064be40208e4f34783/src/ResetPasswordHelper.php#L126If anyone has any questions, let me know :)
Cheers!
Hello!
Is it possible to have 2 types of tokens in the same app, in API Platform 3 :
a jwt token for the users: this is a 'user specific' token. A user gets a jwt token from the server after successful authentication with his email and password.
an 'api token' that would be used by some cron jobs. This api token would be a way to authenticate the cron job. This token would be a 'company' specific token, not a user token, as the cron job is run for a company not for a user. The api token would be either in the URL of the end point (a string), either (better) in the headers of the request run by the cron job. And basically the api token would not change over time: it is a string, specific to a company, and it never expires, so no need for any refresh token.
So can we have simultaneously 2 differents mechanisms of authentication in the same app : classical jwt/refresh token for the users and api tokens for cron jobs (one api token per company) ?
Thanks!
Hey @Marko!
Sure, I think you could definitely do this :). The easy part is creating the 2 tokens. You can make a JWT after the user authenticates successfully. And you could, for example, add an
apiTokenproperty to someCompanyentity and allow an admin for aCompanyto see this in some area on your site (where they could then copy and paste this for their CRON jobs).The trickier part is consuming the tokens, well, maybe not SO tricky I hope :). You could have one authenticator that handles the JWT. This is your normal situation. Then, you could create a 2nd custom authenticator that handles your company token. If these tokens are sent in different ways (e.g.
Authorizationheader for JWT vs some other header for company token), that'll make your job a little easier because each authenticator can figure out "is this a token I should authenticate or is it the other type?". But you could also accomplish this by giving each token some prefix - e.g.company_- and using that.Anyway, I think the truly tricky part is this: when using a JWT, you are authenticating as a
User. So, aUserobject is what you would return from your authenticator... and then anywhere in your code where you say$this->getUser(), it would be aUserobject. But for the company token, your authenticator would return aCompany, and when you use$this->getUser()in your code, that will return aCompanyobject. It's THAT difference that could cause some confusion in your code.I can see two general ideas for solving this:
1) If the company token is only meant to be used for a few specific endpoints, then I might put those few endpoints under their own Symfony firewall. Then, in those endpoints, just be sure that if you're ever referencing the currently authenticated "user", that you know it's the Company.
2) Code carefully :). If you need to be able to support both a
Useror aCompanyacross your entire application, then be sure to create smart security rules that do no depend on the specificUser/Companyobject. I can give more tips about this if you are going this route and have some questions about how to do some specific things.Let me know if this helps!
Cheers!
Hello! Thanks a lot for your answer. One more question : do you confirm that Company should implement User Interface, as a authenticated 'user'? Or not. Thanks again!
Yes it should!
I'm curious in this example, why you have potentially one user - many tokens relationship?
Wouldn't one user - one token suffice ?
Hey @MildDisaster ,
That's how GitHub access tokens, you can create many tokens on GitHub for your account. First of all, you can create different tokens with different scopes, like some tokens may have access to the private repos while others - only public ones. That's the most common and flexible structure I think :) Moreover, you can use one token e.g. for Composer and another token for your website that sends API requests to GitHub. And it's convenient this way, as you can e.g. remove that second token but the first one will still work.
But of course it depends on your personal use case, and if you're happy with OneToOne relation - go for it, there's nothing wrong with it, just some limitations I mentioned above :)
Cheers!
I think I understand. So the relationship is more (but not exclusively) between tokens and apps ( that a user will use to access api with ) ?
What is the preferred way to handle tokens once an associated account is changed, ie (password recovered) ?
Should one invalidate existing tokens, or just let them be ?
I noticed in the symfonycasts github there are projects for account registration and password recovery. Without double checking, don't think there was mention of token cleanup in them.
Thanks !
Hey @MildDisaster ,
Mostly yes, I suppose, but once again, it depends on your specific use case. But from my experience it seems more like this :)
Good question. I think it also depends on your specific use case and your strategy. I don't think GitHub invalidate all tokens if you changed password... but in some cases this might be a good idea. It depends on how much your users will be annoyed by it :)
Yeah, we support those bundles. And yeah, probably it's a good use case when you have to clean up password reset tokens on password change :)
Cheers!
How to authenticate jwt token string (First Way to make token). I have done react login page. Successfully logged in, But not getting Authenticated.
Hey @Someswara-rao-J!
Hmm. Did you create an access token authenticator like we did? https://symfonycasts.com/screencast/api-platform-security/access-token-authenticator
If so, here is what I would do to debug:
A) After making the AJAX request to "log in", find the Symfony profiler for that request and open it (reminder: you can always go to
/_profilerto see a list of the most recent requests to your site - and you can find the AJAX request you just made there. When you get to the profiler for that request, click on the "Security" tab. Does it show you as authenticated?B) If it does NOT show you as authenticated, then look more closely at your access token authenticator system: make sure your
ApiTokenHandleris being called, etc. If it DOES show that you are authenticated... but then future AJAX requests appear to be not authenticated, it means that you are "losing" authentication between requests.There are a few things that could cause this:
1) If you have
stateless: trueon your firewall, this will happen. This is because this tells your firewall to NOT use session/cookie storage.2) If your frontend and backend are on different subdomains this could happen. For example, if your API is api.example.com and your frontend is just frontend.example.com, by default, Symfony will create a cookie for api.example.com and (iirc) that will not be usable by your frontend. You can adjust your cookie domain in the Symfony settings if this is the cause
3) In some situations, if you've added some custom serialization logic to your
Userclass, you can "lose" authentication on the 2nd request. If you check the logs on the first request after the successful login, you should see something like:Let me know if this helps!
"Houston: no signs of life"
Start the conversation!