Laravel generate secure https URL from route - url

Edit
I can't really find a way to generate a secure URL from route name.
To get a full URL, I use
echo route('my_route_name');
But what to do, if I want a URL with https?

UPDATE: As pointed out in the comments, a simpler way of doing this would be adding URL::forceSchema('https'); for Laravel version between 4.2-5.3 or URL::forceScheme('https'); for version 5.4+ in the boot method of your AppServiceProvider file.
Old answer:
It's actually entirely possible and there's only one line of code needed to accomplish that.
Laravel doesn't check the presence of SSL by itself, it depends on Symfony. And there goes our key to making it believe that the current request is secure.
The thing is, we have to set the HTTPS server param to true and the easiest method is to paste the following code in the boot method of your AppServiceProvider:
$this->app['request']->server->set('HTTPS', true);
In my very case, I only need to force SSL in production, the local env should still work on http. This is how I force SSL only on production:
$this->app['request']->server->set('HTTPS', $this->app->environment() != 'local');
By the way, mind those terms, you may need them in the future.

Laravel 8
I recently resolved this by modifying this file:
app/Providers/AppServiceProvider.php
in the method boot() add the following:
URL::forceScheme('https');
Add the use in the top:
use Illuminate\Support\Facades\URL;
to work in your local environment you can leave it like this:
public function boot()
{
if(env('APP_ENV') !== 'local') {
URL::forceScheme('https');
}
}
Note: Don't forget to set your env variable APP_ENV with prod for the production file.
APP_ENV=prod

Actually turns out, that laravel doesn't care if url is secure or not, because it generates based on the current url. If you're on https page, route() will return secure url. If on http, then http:// url
The problem was, that Laravel didn't detect that https was enabled, which was due to faulty server configuration.
You can check if Laravel sees the current connection as https by calling Request::isSecure();

As I mentioned in a relevant question, I found 5 ways of how to generate secure URLs.
Configure your web server to redirect all non-secure requests to https. Example of a nginx config:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Set your environment variable APP_URL using https:
APP_URL=https://example.com
Use helper secure_url() (Laravel5.6)
Add following string to AppServiceProvider::boot() method (for version 5.4+):
\Illuminate\Support\Facades\URL::forceScheme('https');
Implicitly set scheme for route group (Laravel5.6):
Route::group(['scheme' => 'https'], function () {
// Route::get(...)->name(...);
});
At the moment this way is not documented, but it works well.

I think there is only one way to do this.
To generate the secure URL to your named routes, you might want to pass in your route into the secure_url helper function.
secure_url(URL::route('your_route_name', [], false));
You can't really use the route helper function because it generates absolute URL (with http://) by default and it's http not the https version that you wanted

Laravel 5.x will generate secure URL via route() helper if it detects the incoming connection is secure. Problem usually happen if the app is hidden behind load balancer or proxy (e.g. Cloudflare) since the connection between app server and load balancer/proxy might not be secure.
I am using Laravel Forge + Cloudflare now and this is the easiest way I could find to enable app thinking incoming connection is secure (not sure about other proxy).
Generate self signed certificate (see https://www.digitalocean.com/community/tutorials/openssl-essentials-working-with-ssl-certificates-private-keys-and-csrs or http://www.selfsignedcertificate.com/)
In Forge panel, insert your private key and cert via Sites > your-site > SSL Certificates > Install Existing Certificate.
Activate
In CloudFlare panel, Crypto > SSL, choose “Full” (not strict)
Done (it will take few minutes for the change to get propagated)
In short, connection between client and Cloudflare is secured by Cloudflare's own SSL. Connection between app server and Cloudflare is protected via your generated cert (thus the app is seeing 'connection' as secure.
You can apply the same principle with other stacks.

Use secure_url:
secure_url(URL::route('your_route_name', [], false));
You will need to set URL::route to false in order to not return a full URL. Then use secure_url function generates a fully qualified HTTPS URL to the given path.
From the UrlGenerator interface you can use URL::route
string route(string $name, mixed $parameters = array(), bool $absolute = true)
Get the URL to a named route.
Parameters
string $name
mixed $parameters
bool $absolute
Return Value
string
https://laravel.com/api/5.4/Illuminate/Contracts/Routing/UrlGenerator.html

In most cases routes should be generated with the same scheme your site was loaded with. Laravel automatically detects if request has X-Forwarded-Proto header and uses it to decide which scheme to use in generated route URLs. If your site is behind reverse proxy then you should add reverse proxy IP address to list of trusted proxies. https://github.com/fideloper/TrustedProxy package helps to do this. It's included in Laravel 5.5. For example, my config/trustedproxy.php looks like:
<?php
return [
'proxies' => '*',
'headers' => [
]
];
I use it with nginx reverse proxy that has the following configuration:
server {
listen 80;
server_name example.com;
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
client_max_body_size 50m;
location / {
proxy_pass http://localhost:8002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
}
Replace example.com with your domain. SSL certificates was provided by Let's Encrypt with certbot.

On laravel 5.5.*
You only need to add https on your .env file
as AppServiceProvider already had function that checks if your APP_URL or app.url on your config has https on it.
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
\URL::forceRootUrl(\Config::get('app.url'));
if (str_contains(\Config::get('app.url'), 'https://')) {
\URL::forceScheme('https');
}
}

This is certainly old, but someone like me will dump over here one day.
In your .env file define the APP_URL to use https instead of using http. Because all laravel url are generated based on this variable.
APP_URL=https://example.com
and wherever you want you can just say
{{ URL::route('my.route', params) }}
Or
{{ route('my.route', params) }}
With make sure all the routes are generated with secure protocol, add in the boot method of AppServiceProvider class:
<?php
namespace App\Providers;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot(UrlGenerator $url)
{
if (config('app.production')) {
$url->forceScheme('https');
}
}

Just add your application domain with the https protocol in the APP_URL of your .env file.
APP_URL=https://example.com
Then run route:cache

For reference of future visitors:
The secure_url function doesn't correctly handle GET parameters. So, for example, if you want to convert the url that the user has visited into a secure url while retaining the GET fields, you need to use this:
secure_url(Request::path()).'?'.http_build_query(Input::all());
Particularly note the use of path() rather than url() - if you give it a full url, it doesn't replace the http at the start, making it efectively useless.

I came across this issue while trying to generate a route as form action in Blade using Laravel 5.4.
Then I hit upon secure_url(), so I tried
{{ secure_url(route('routename', $args)) }}
This still returned a non-secure URL. :-(
After digging through the code and adding some debug logs, I finally figured out that secure_url does not change the incoming url argument, if it's already an absolute URL (including the scheme).
Fortunately route has an absolute flag as the third argument, and returns a relative URL if $absolute is passed as false.
Assuming /a/{id}/b is a named route "a.b"
route('a.b', 1) : will return http://[domain]/a/1/b
route('a.b', 1, false) : will return /a/1/b
Joining the two I arrived at :
{{ secure_url(route('routename', $args, false)) }}
As expected it generated https://[domain]/routeXXX
:-)

I had a problem with redirect trailing slashes after 2 hours of looking for a bug, just need to remove
.htaccess
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
to
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

If you are using Load Balancer, Laravel will never have the actual schema available.
So use https://stackoverflow.com/a/65691937/6489768. Working with Laravel - 9.x

Place this in your filters.php file and everywhere will be forced to https while retaining URL parameters:
//force ssl
App::before(function () {
if(!Request::secure() && App::environment() != 'local')
{
$baseHost = Request::getHttpHost();
$requestUri = Request::getRequestUri();
$newLink = 'https://'.$baseHost.$requestUri;
return Redirect::to($newLink);
}});

According to the laravel documentation on the url() helper method.
If no path is provided, a Illuminate\Routing\UrlGenerator instance is
returned
So you can use the secure method of the UrlGenerator class in the following way:
echo url()->secure('my_route_name');

To generate a secure (https) route use the following built-in 'before' filter called 'auth':
For example:
Route::get('your-route', ['before' => 'auth', 'uses' => YourController#yourAction']);
Now when you output your link it will be prepended with 'https'

Related

Nginx redirect for a subdomain

does anyone know how the interaction works in Nginx?
I currently have a subdomain, let's call it subdomain1, I want to change it to subdomain2.
To be more specific.
I run everything in a docker container and my certificate will be for subdomain2. And there will be no more servers with subdomain1.
I want to keep the traffic from google for subdomain1, but the name is not appropriate anymore and it needs to be changed to subdomain2.
Does something like this work? Will there be any issues?
server {
server_name subdomain1.mydomain.com;
return 301 http://www.subdomain2.mydomain.com/$request_uri;
}
Something like that could match :
server {
listen 8066;
server_name localhost;
location / {
rewrite (.*)$ http://www.google.com$1 redirect;
}
}
8066 is for my test purpose to redirect to google.com.
If y try localhost:8066/foo, I go to https://www.google.com/foo
Note that redirect keyword makes it temporary. For a permanent redirection, use permanent instead.
Yes, your approach will work. Following points might be helpful:
Since you want not to have any server for subdomain1 but in this redirection you need to ensure that subdomain1 also pointing to the same server where you have hosted subdomain2
use of $scheme
server { server_name subdomain1.mydomain.com; return 301 $scheme://subdomain2.mydomain.com$request_uri; }
Generally people avoid using www before sub-domain.domain.com (you may refer this also)
Section server in nginx has two required parameters listen and server_name. Add listen to your config and it will work
Man about server https://nginx.org/en/docs/http/ngx_http_core_module.html#server
Example
server {
listen 8080;
server_name _;
return 301 http://www.google.com/$request_uri;
}

Multiple Docker Containers Hosted In Nginx With URL Instead Of Subdomain

I'm a noob to docker, Nginx, and devops, so go easy on me.
I've followed a few tutorials that show me how to host multiple web apps through docker containers using Nginx and subdomains. I cannot create a new A Record for this domain, so I can't use subdomains, it has to be a url. If I could create a new A Record, I found a million tutorials that show me how to host it on ProjectA.example.com but since I don't have access to create a new A Record for the domain, I need to find a way to host it on something like example.com/ProjectA. Another obstacle is only port 80 is open to the outside, so all traffic must come through port 80 and be reverse proxied to whatever port the docker container is forwarding from.
So far I have an Nginx configuration that looks something like this
server {
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
listen 80;
server\_name \_;
location / {
try\_files $uri $uri/ =404;
}
location /projectA {
proxy\_pass http://127.0.0.1:9001/;
}
location /projectB {
proxy\_pass http://127.0.0.1:9002/;
}
}
This works getting me to the homepage of the project. But the CSS of the website doesn't load, and whenever I click a link, it sends me to something like example.com/signup instead of example.com/projectA/signup. I tried making a wildcard location (location \~ /projectA.\*) but Nginx didn't like that. I was thinking there's probably a way I could get something like if the referring uri contains projectA, send them to example.com/projectA$uri but I couldn't find the documentation on the syntax.
Basically the question is, is this a good way to tackle the problem, and does anyone have a link to a tutorial or some documentation on how to do this?
Using a trailing slash behind location should do it:
location /projectA/ {
proxy_pass http://127.0.0.1:9001/;
leads /projectA/whatever to http://127.0.0.1:9001/whatever
If you want to use regex to rewrite, it's something like that:
location ~ ^/projectA/(.*)$ {
proxy_pass http://127.0.0.1:9001/$1;
or
location /projectA/ {
rewrite ^/projectA/whatever/(.*)$ /whatever.php?path=$1 break;
proxy_pass http://127.0.0.1:9001/;
}
leads /projectA/whatever/foo to http://127.0.0.1:9001/whatever.php?path=foo

How does Nginx proxy_pass pass a route parameter

I'm trying to make an api call to an internal docker container, but for every request url I have to make a proxy_pass in the Nginx config. I've read articles that the slashes at the end should work to pass all after de certain url to the proxy_pass.
Read here (redirect table)
Example
www.example.com/api -> redirects to correct endpoint
www.example.com/api/2020 -> this doesn't redirect to http://api/2020
Configuration
location = /api/ {
proxy_pass http://api/;
}
So why doesn't this configuration pass the 2020 'parameter' to the api endpoint? It works when I make a configuration like this:
location = /api/2020 {
proxy_pass http://api/2020;
}
But the problem is that it's a parameter so it can possibly be any number, how to solve this?
I've read other posts, but I ask this question again to get a broader understanding of the passing possibilities for parameters. Is it really necessary to use Regex for this?
Remove exact matching, just use
location /api/ {
proxy_pass http://api/;
}
without any regexes.
You are using "=" regex for comparison so It will find same url so please read the below code & change your configuration.
location ~ ^/(api)/ {
proxy_pass http://api;
}
After the above changes restart your nginx server & you dont need to write separate code for all the APIs.
I hope!
It will resolve your problem.
This is very easy to solve:
location / {
proxy_pass http://internal_addr:port$request_uri;
}
Example for a IP/PORT: 172.168.1.1:3000, internal server:
location / {
proxy_pass http://172.168.1.1:3000$request_uri;
}
By doing this, everything that the external client requests to nginx after the / (routes, parameters, etc.) nginx will forward to the internal server in exactly the same way.
If you have more than one internal server, you can use something like this:
In server1(IP/PORT: 172.168.1.1:3333):
location /app1 {
proxy_pass http://172.168.1.1:3333$request_uri;
}
That is:
Client Request to Nginx: exemple.com/app1/login.php?x=y
Nginx Will send the request to server1 as /app1/login.php?x=y
In server2 (IP/PORT: 172.168.1.2:4444):
location /app2 {
proxy_pass http://172.168.1.2:4444$request_uri;
}
That is:
Client Request to Nginx: exemple.com/app2/login.php?x=y
Nginx Will send the request to server2 as /app2/login.php?x=y

Nginx Rewrite URL to contain the wildcard subdomain and change domain suffix at the same time

Need some help... I need to rewrite urls for local images in nginx. Here are some rules.
If the image isn't on the file system then rewrite the url.
The url can be a a wildcard subdomain
The domain suffix needs to be changed to .com
So as an example
a.example.dev -> a.example.com
b.example.dev -> b.example.com
*.example.dev -> *.example.com
At the end I need to append the image url, here is a full example
Original
http://www.engineering.example.dev/files/2015/05/filename.gif
Final
http://www.engineering.example.com/files/2015/05/filename.gif
Original
http://www.example.dev/files/2015/05/filename.gif
Final
http://www.example.com/files/2015/05/filename.gif
Any help would be greatly apprechiated.
The regular expression server name can collect the wildcard subdomain in a named capture. You can then use try_files to conditionally redirect to the .com domain.
For example:
server {
server_name "~^(?<name>.+)\.dev$";
root /path/to/root;
location / {
try_files $uri #redirect;
}
location #redirect {
return 301 $scheme://$name.com$request_uri;
}
}

Ajax call triggering base URL in Apache + Passenger integration mode

I am trying to configure multiple RAILS apps through httpd configuration file. Everything is working fine but the AJAX calls are triggering the wrong URL, for example if the application is configured as
http://localhost/helloapp/
and it has AJAX call as get "/say_hello"
it is trying to get "localhost/say_hello" instead of "localhost/helloapp/say_hello".
Below is my httpd configuration file located at '/etc/httpd/conf/httpd.conf
'. I am using centOS.
<VirtualHost *:80>
ServerName localhost
<Directory /var/www/html >
Allow from all
Options -MultiViews
# Uncomment this if you're on Apache >= 2.4:
#Require all granted
</Directory>
Alias /helloapp /var/www/html/hello_application/public
<Location /helloapp>
PassengerBaseURI /helloapp
PassengerAppRoot /var/www/html/hello_application
</Location>
<Directory /var/www/html/hello_application/public>
# MultiViews must be turned off.
Allow from all
Options -MultiViews
</Directory>
</VirtualHost>
Any time you make an HTTP request to a url beginning with /, for example /say_hello, it is implied that you are sending a request to the path you specify from the root URL. The root URL is always your domain, which in your case is http://localhost/.
So no matter how you configure your Apache server, when you make a request to /say_hello, your browser will direct it to http://localhost/say_hello. If you want to make a request to http://localhost/helloapp/say_hello, you will have to tell ajax to go to /helloapp/say_hello in your website code.
If your rails app is named "helloapp", you could get this in one of the ways described here to access your application name in your rails code. Once you have it you could store it in an instance variable, like #app_name, for your template or javascript (or wherever you are making the ajax request from). I think what you are looking for is something along these lines:
var url = "/<%= #app_name %>/say_hello";
// do your ajax request with your new url variable
One way is to modify the urls for all ajax call in all js files and adding #app_name in the urls.
If you don't want to edit all ajax urls in all js file.Write the following piece of code in a separate js file or in application.js file:
$.ajaxSetup({
beforeSend: function(data, settings) {
var url = "/#app_name"+ settings.url;
settings.url = url;
}
});

Resources