Why is request()->isSecure() returning false when visiting a HTTPS-url? I am using Laravel Forge and I have a load balancer. Logging the request I get this data:
request()->url(), // "http://xx.xx"
request()->isSecure(), // false
request()->getClientIps(), // XX.XX.XX.XX
In TrustProxies (which is added in the $middleware array in Http/Kernel.php):
protected $proxies = [
'XX.XX.XX.XX', // exactly the same as in the logged data above
];
In my AppServiceProvider's boot() method:
if (env('APP_ENV') == 'production') {
\URL::forceScheme('https');
}
Edit:
I am also using Cloudflare, and I have added all the Cloudflare proxies to $proxies as well. Check the logs, I see these headers in the request:
X-Forwarded-Proto: http
Cf-Visitor: {"scheme":"https"}
Referer: https://xx.xx/someurl
While You are using Cloudflare You should to use "Full SSL/TLS encryption mode".
Related
I know I'm not the first struggling with this. But after some days and going trough a lot of related questions i somehow feel that my case deserves it's own question :).
I have a working websocket solutions with Laravel Websockets (https://beyondco.de/docs/laravel-websockets/getting-started/introduction) and Laravel Echo for public channels.
My client application is a vue-cli app and connects to the server + broadcast messages on public channels work great. The authorization is handled by Laravel Passport. So through sending a Bearer token in the Authorization header the backend application knows if the user is authenticated.
However I'm struggling to get Private channels to work. Trying to authenticate always gives me this error:
Access to XMLHttpRequest at 'https://my-app.test/broadcasting/auth' from origin 'https://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I know this CORS error comes on my way when I have a server issues so I tried debugging the request in Insomnia. However when mimicking the request in Insomnia it gives a response 200 and also what would be expected:
I've been reading several guides and stackoverflow questions but I can't find anything similar.
Going back to it might be a CORS issues but I don't think that is the case. My OPTIONS request returns back just fine.
To be complete I also add some code that might help in debugging.
My BroadcastServiceProvider
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
My channels.php
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
The client
this.echoClient = new Echo({
broadcaster: 'pusher',
key: process.env.VUE_APP_WEBSOCKETS_APP_ID,
wsHost: process.env.VUE_APP_WEBSOCKETS_URL,
wssPort: 6001,
// forceTLS: true,
disableStats: true,
authEndpoint: process.env.VUE_APP_SERVER_URL + '/broadcasting/auth',
auth: {
headers: {
Authorization: "Bearer " + this.$store.state.auth.auth_token
}
}
})
// this one works!!
this.echoClient.channel('App.User')
.listen('UpdatePosts', (e) => {
console.log('event received')
console.log(e)
})
// private channels don't work ... :'(
this.echoClient.private('App.User.' + this.user.id)
.listen('UpdatePosts', function(e) {
console.log(e)
})
for anyone struggling with this issue.
For me the solution was to add the api prefix to the broadcast::auth method.
public function boot()
{
Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
Offcourse you need to correctly set the api prefix on the client:
authEndpoint: process.env.VUE_APP_SERVER_URL + '/api/broadcasting/auth',
I suppose the difference is that when you prefix api Laravel we specifically tell the server to ditch web Middleware.
I still don't really understand why the request was succesfull from Insomnia since there was no x-csrf header set. Insomnia did send a cookie header. Maybe that's the reason why it was working there.
EDIT
Solution provide by #Tippin on laracasts forum.
To add to the answer, it was a CORS issue after all.
https://github.com/fruitcake/laravel-cors
Prefixing the broadcast route with API does not alter middleware at all, so that is not putting it in the api middleware group. What I do think is happening is you may have the cors package installed and in the allowed paths, you have something like api/*, so by simply adding that prefix, you solved your issue. Otherwise, you can add the default broadcast to the whitelist (assuming you use that package for CORS):
/*
* You can enable CORS for 1 or multiple paths.
* Example: ['api/*']
*/
'paths' => ['api/*', 'broadcasting/auth'],
https://github.com/fruitcake/laravel-cors/blob/master/config/cors.php
I've got the same problem
By using fruitcake/laravel-cors, it was solved.
this is my auth option:
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},
I know it's a known issue but I've tried almost everything and I'm still stuck on this. I have a simple project structured like this:
[Client] => [Gateway] => [API]
Laravel 6 Lumen 6 Lumen 6
localhost:8000 localhost:8001 localhost:8002
Since I'm just started working on this project only to prove if this works I've disabled all auth stuff.
On my API I have a folder within public called uploads (Basically in http://localhost:8002/uploads/audio.amr) where I have 1 audio file (.amr) and I'm trying to play it from a client view.
Since html can't play .amr files, I had to use a plugin. And I'm using this one BenzAMRRecorder.
[Client side]
I make an ajax petition to get the url of the audio file. The client through guzzle connects with the gateway and the gateway also does it with the API and I successfully got the url http://localhost:8002/uploads/audio.amr.
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
url : 'client/get_url_audio',
type : 'GET',
data : {
},
dataType:'json',
success : function(data) {
/** Here's the way to play the file */
var amr = new BenzAMRRecorder();
amr.initWithUrl(data['url']).then(function() {
amr.play();
});
},
});
I successfully got the url but when the BenzAMRRecorder try to access to the url http://localhost:8002/uploads/audio.amr I got this error:
The error:
Access to XMLHttpRequest at 'http://localhost:8002/uploads/audio.amr' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I've read a lot of ways to fix this and I added a CorsMiddleware on the API with a handle function as follows:
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With'
];
if ($request->isMethod('OPTIONS'))
{
return response()->json('{"method":"OPTIONS"}', 200, $headers);
}
$response = $next($request);
foreach($headers as $key => $value)
{
$response->header($key, $value);
}
return $response;
}
And then on bootstrap/app.php added
$app->middleware([
App\Http\Middleware\Cors::class
]);
But I'm still getting the same error. The thing I thought is that, when the method amr.initWithUrl(data['url']) access to the API folder, it doesn't go to middleware and try to access directly to the folders without passing by the middleware but I don't know why. Can someone help me to solve this problem?
EDIT: I also tried with github.com/barryvdh/laravel-cors
Add the following in the .htaccess file from the server which holds the resource you are trying to access:
Header Set Access-Control-Allow-Origin "*"
I don't know if it works in Lumen, but for Laravel, I've had a lot of success using this neomerx/cors package.
You probably missed the header X-CSRF-TOKEN from your CORS middleware?
$headers = [
....
// You will need to add ALL headers sent from your client
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With, X-CSRF-TOKEN'
];
Message error:
Mixed Content: The page at 'https://example/contracts/create' was
loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint
'http://example:443/api/v1/contracts'. This request has been blocked;
the content must be served over HTTPS.
I saw some similar posts, but till now it didn't work.(my .env is with https, and this is not local)
I got this error just on this post, the rest of them are with https, and only when I have error validation.
On local it's working fine.
Route::middleware('auth:api')->prefix('contracts')->group(function () {
Route::get('{contract?}', 'ContractsController#getContracts');} //in api.php
post("/contracts/" + `${this.customer_id}`, newContract) // in VueJs
You can add to app\Provider\AppServiceProvider
/**
* Register any application services.
*
* #return void
*/
public function register()
{
if (!app()->isLocal()) {
URL::forceScheme('https');
}
}
While working on a project I found third party API's are working from Postman, but doesn't work from Guzzle Client.
Debugging a Guzzle request could be difficult, so is there any way that can log all requests made by Guzzle client can be seen?
TLDR;
There’s an easy way to log all Guzzle requests by passing second parameter to Client and then it will log all the requests. But that’s ugly way if you have many methods using Guzzle Client to send request to third party server. I’ve done it using Laravel’s Service Container.
Long way via Laravel’s Service Container
When I used Guzzle client in my project and used handler to log all requests it looks good. But later on there were many methods in many different classes so I have to write logger logic every where. Then I thought why don’t to leverage Laravel’s Service Container and bind an object once and use it everywhere.
Here’s how I did it. In your AppServiceContainer.php’s boot method we will add all our code. And then in Controllers we will use our Client object.
Add this use statements on top of the AppServiceContainer.php file.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use Illuminate\Support\ServiceProvider;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
Add below code to your AppServiceContainer.php’s boot method
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app->bind('GuzzleClient', function () {
$messageFormats = [
'REQUEST: {method} - {uri} - HTTP/{version} - {req_headers} - {req_body}',
'RESPONSE: {code} - {res_body}',
];
$stack = HandlerStack::create();
collect($messageFormats)->each(function ($messageFormat) use ($stack) {
// We'll use unshift instead of push, to add the middleware to the bottom of the stack, not the top
$stack->unshift(
Middleware::log(
with(new Logger('guzzle-log'))->pushHandler(
new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
),
new MessageFormatter($messageFormat)
)
);
});
return function ($config) use ($stack){
return new Client(array_merge($config, ['handler' => $stack]));
};
});
}
Explanation
If you have noticed above code, In first line of boot method we are telling Laravel that we want to register this code as a GuzzleClient in your Service Container.
In last return statement we are returning a function that will accept one argument $config. We used this function as a proxy so that we can pass an argument to it and that can be used in Client Object.
return function ($config) use ($stack){
return new Client(array_merge($config, ['handler' => $stack]));
};
Rest of the code is building Guzzle’s handler object to Log all requests to a file called guzzle-log.log using Logger object of Monolog library. If you have daily logs enabled, a date will be appended to file name like guzzle-log-2019-08-11.log.
Usage
We have binded our object to Service Container, now it’s time to use this container everywhere in our code, and make it looks clean.
For demo purpose I’ve used it directly in routes/web.php file. You can use anywhere.
Route::get('/', function () {
$client = app('GuzzleClient')(['base_uri' => 'http://httpbin.org/']);
$request = $client->get('get',[
'query' => ['foo'=>'bar', 'baz' => 'baz2'] ,
'headers' => [ 'accept' => 'application/json']
]);
$response = json_decode((string) $request->getBody());
return response()->json($response);
});
As you can see I’m making an object $client using app() helper. Also you can pass any valid arguments array that Guzzle client supports as a second parameter. Here I’ve passed base_uri.
Source: http://shyammakwana.me/laravel/laravel-log-guzzle-requests-to-file-using-service-container.html
The accepted answer works nicely by using Service Providers. Another option would be to attach GuzzleHttp\Middleware's log to wherever you use Illuminate\Support\Facades\Http. An example which logs the request, the response, and any errors if found would be to use Middleware::log:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
/**
* Class TestService
* #package App
*/
class TestService
{
private function getAccessToken()
{
try {
$response = Http::asForm()->withMiddleware(Middleware::log(with(new Logger('guzzle-log'))->pushHandler(
new RotatingFileHandler(storage_path('logs/guzzle-log.log'))
), new MessageFormatter(MessageFormatter::DEBUG)))->post("https://test.com/oauth/v2/token", [
'grant_type' => 'client_credentials',
]);
$response->throw();
} catch (\Throwable $th) {
$accessToken = false;
}
return $accessToken;
}
}
This will write log records to logs/guzzle-log-{currentDate}.log file. The format of the log record I used in this example is MessageFormatter::DEBUG which is ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}" which nicely outputs the request, response, and any errors. an example of a log file would be:
[2020-08-07T07:13:23.712124+00:00] guzzle-log.INFO: >>>>>>>> POST /oauth/v2/token HTTP/1.1 Content-Length: 29 User-Agent: GuzzleHttp/7 Host: xxxx:4000 Content-Type: application/x-www-form-urlencoded grant_type=client_credentials <<<<<<<< HTTP/1.1 200 OK X-DNS-Prefetch-Control: off X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=15552000; includeSubDomains X-Download-Options: noopen X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Access-Control-Allow-Origin: * Content-Type: application/json; charset=utf-8 Content-Length: 113 ETag: W/"71-DyA+KEnetTKfUlb0lznokGTt0qk" Date: Fri, 07 Aug 2020 07:13:23 GMT Connection: keep-alive {"data":{"token_type":"Bearer","access_token":"XYZ","expires_in":"7776000"}} -------- NULL [] []
Note: The downside to this option is that you will have to attach withMiddleware(Middleware::log(...)) to anywhere you are using Illuminate\Support\Facades\Http.
I have an application which uses AJAX quite a bit on a Laravel 5.3 application. I have some pages that are behind authentication and some that are not. The ones that are inside of authentication are working fine. The one that is outside (public facing) are giving me a the infamous TokenMismatchException in VerifyCsrfToken.php line 68. In order to attach the token to the AJAX header, I am using this...
$.ajaxSetup({
cache: false,
async: true,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
And it is working because when I make a request, I can see this...
...but the tokens are not matching. When I go to the framework file Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class and do a dd() on the session token and the token that was passed, they do not match.
Things I have tried:
Clearing Cache (views, config, cache)
Changing session driver from
file to Redis
Using csrf_field() instead of AJAX headers
I cannot figure out why this is not working. Any other ideas?
If you look at this code of laravel Github Link
/**
* Determine if the session and input CSRF tokens match.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function tokensMatch($request)
{
$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
if (! is_string($sessionToken) || ! is_string($token)) {
return false;
}
return hash_equals($sessionToken, $token);
}
It checks for the X-CSRF-TOKEN and also tries to check for X-XSRF-TOKEN. You can also try to send the _token from the ajax. I hope this helps you.
And, I finally figured it out. I am using BrowserSync for livereload, which proxies all my requests to localhost:3000/*. When I was testing the public side, I was visiting it through the original domain name and not proxied through browsersync's localhost:3000 so that was causing session issues.
Basically, if you have BrowserSync running and you try in use your site not through browsersync, you can get token mismatch errors.