If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Guess what? You could deploy this code right now. Sure - we have a lot more to talk about - like subscriptions & discounts - but the system is ready.
Oh, but there's just one thing that you cannot forget to do. And that's to force
https
to be used on your checkout page.
Right now, there is no little lock icon in my browser - this page is not secure. Of course it's not a problem right now because I'm just coding locally.
But on production, different story. Even though you're not handling credit card information, Stripe does submit that token to our server. If that submit happens over a non-https connection, that's a security risk: there could be somebody in the middle reading that token. Regardless of what they might or might not be able to do with that, we need to avoid this.
There are a lot of ways to force HTTPs, but let me show you my favorite in Symfony.
In OrderController
, right above checkoutAction()
, this @Route
annotation is
what defines the URL to this page. At the end of this, add a new option called
schemes
set to two curly braces and a set of double-quotes with https
inside:
... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
... lines 14 - 27 | |
/** | |
* @Route("/checkout", name="order_checkout", schemes={"https"}) | |
... line 30 | |
*/ | |
public function checkoutAction(Request $request) | |
{ | |
... lines 34 - 68 | |
} | |
} |
OK, go back and refresh! Cool! Symfony automatically redirects me to https. Life is good.
Wait, life is not good. I hate needing to setup SSL certificates on my local machine. I actually have one setup already, but other developers might not. That's a huge pain for them... for no benefit.
Fortunately, there's a trick. Replace https
, with %secure_channel%
:
... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
... lines 14 - 27 | |
/** | |
* @Route("/checkout", name="order_checkout", schemes={"%secure_channel%"}) | |
... line 30 | |
*/ | |
public function checkoutAction(Request $request) | |
{ | |
... lines 34 - 68 | |
} | |
} |
This syntax is referencing a parameter in Symfony, so basically a configuration
variable. Open parameters.yml
, add a new secure_channel
parameter and set it
to http
:
... lines 1 - 3 | |
parameters: | |
... lines 5 - 22 | |
# set to https on production | |
secure_channel: http |
And as you know, if you add a key here, also add it to parameters.yml.dist
:
... lines 1 - 3 | |
parameters: | |
... lines 5 - 22 | |
# set to https on production | |
secure_channel: http |
Ok, head back to the homepage: http://localhost:8000
and click to checkout. Hey!
We're back in http
. When you deploy, change that setting to https
and boom,
your checkout will be secure.
So there's your little trick for forcing https without being forced to hate your life while coding.
Yo man,
Hm, looks like Symfony can't resolve its value. Have you add the "secure_channel" parameter in your app/config/parameters.yml.dist and *also*, what more important, in app/config/parameters.yml file? Also, please, try to clear the cache manually for both dev/prod environments.
Cheers!
Hey Blueblazer172!
Hmm, so I just re-checked the code - pulled the code down to this step, and tried it again. It does work fine for me. So, a few things to try out :)
1) If you run php bin/console debug:router order_checkout
, what is the value next to Scheme?
2) If you remove the "secure_channel" from parameters.yml, do you get an error (you should, you should get an error that the parameter secure_channel must be defined). If you don't, it definitely makes me think that - for some reason - the parameter isn't being used.
3) Could you paste your @Route + controller code here?
Cheers!
Hey Blueblazer172 !
Sorry for my late reply! So, there are two weird things happening, and they seem to conflict with each other:
A) because you remove secure_channel from parameters.yml and do NOT see any error, it means that Symfony does not see %secure_channel% referenced anywhere. In some ways, that's no surprise - it seems like Symfony is not replacing this value for some reason.
B) But, the fact that the "Scheme" when you run bin/console debug:router order_checkout
says HTTP is puzzling. By default (if you don't set schemes), this will say "All". Since it says "http", it means that this value IS being set.
So, it's a mystery. It seems to be set in some cases, but not others - and that's just not how Symfony's routing system works: one set of routes are compiled and used everywhere. Is it possible that you have a second route defined accidentally with the same name - order_checkout
? Also, you mentioned on your first post that when you click "Checkout", you get the wrong behavior, but when you type http:/localhost:8000/checkout and hit enter, it works as expected. Can you post the code for your link? The code for your controller looks perfect. If you change the "schemes" foryour @Route to "https" and run bin/console debug:router order_checkout
, does the Schemes value change?
I'm sure it's something small. This behavior does work - it must be some small configuration we're missing!
Cheers!
Hi Ryan,
thanks for taking your time on solving this issue.
I have no other route with the same name :)
I have put all files in this github project maybe you can find the issue there
https://github.com/Blueblaz...
And yes it changes to https if i change it :)
So what I think is that there must be something wrong with my @Route or generally with the Controller or the twig file
Regards
Hannes
Hi Hannes!
Ah, thank you! Full code - that is the BEST way to help debug. And, I found the problem thanks to it! It's actually a big in Symfony (https://github.com/symfony/... which has a pending pull request to fix (https://github.com/symfony/....
Here's the situation:
A) Whenever you change a routing file (or controller with annotations), Symfony is smart enough to know that it needs to rebuild the routing cache.
B) But, when you change the parameters.yml (e.g. to change from http to https or vice versa), the routing cache is NOT rebuilt. In other words, when you change your parameter from http to https, the old routing cached (the one that thinks the scheme is set to http) is still set.
This problem only affects the dev environment. And the fix even in dev is easy - either clear your cache or simply modify any line in your controller file and then refresh. This will trigger your route cache to be rebuilt.
It's not often you can find a Symfony bug, so... congrats?? :). I've just commented on the pull request to fix it - to see if we can get a version of it merged.
Cheers!
Hey Dmitriy,
Actually, almost the same way. But instead of setting this parameter in parameters.yaml.dist and parameters.yaml files put it in config/services/yaml under "parameters:" key. Well, in Symfony 4 you still can create the same plain variables under the "parameters:" key. But if you want to make its value server-specific, set the value of the parameter with "%env('SECURE_CHANNEL')%" and declare this env var in your .env and .env.dist files.
So the main idea is to get the proper value from an environment variable, and set the value to a parameter which you'll be able to use in @Route() annotation.
Cheers!
Thank you, Victor.
I solved the problem with this code inside the public/.htaccess file
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Now all requests on http are redirected to https.
Great! And actually doing it on the server's level is even better I think. Thanks for sharing it with others!
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9, <7.4",
"symfony/symfony": "3.1.*", // v3.1.10
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
"stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
"stripe/stripe-php": "^3.15", // v3.23.0
"doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
"twig/twig": "^1.24.1", // v1.35.2
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.2
"hautelook/alice-bundle": "^1.3", // v1.3.1
"doctrine/data-fixtures": "^1.2" // v1.2.1
}
}
I'm back :)
I have the exact code as ryan, but when i click on checkout. this error occurs:
No route found for "GET /%secure_channel%://localhost/checkout" (from "http://localhost:8000/")
but when i type localhost:8000/checkout and hit enter is works.
has to be an easy problem :)