Why I cant initialize a Channel using Vue? - php

I am having trouble when I'm trying to initialise a Channel.
I've followed some tutorials provided (https://getstream.io/blog/chat-messaging-with-laravel/, https://getstream.io/blog/tutorial-build-customer-support-chat-with-laravel-vue-and-stream/) that have a stack as mine (Laravel + Vue)
I am already getting the token on the backend, initializing the Client, setting the User and the token on the client.
But when I try to do this.channel.watch(); or even a simple channels query like
const filter = { type: 'messages', id: '1000056864'};
const sort = { last_message_at: -1 };
const channels = await this.client.queryChannels(filter, sort, {
watch: true,
state: true,
});
It will return to me the error as follows:
Access to XMLHttpRequest at 'https://chat-us-east-1.stream-io-api.com/channels/messages/1000056864/query?user_id=62&api_key=2e******e2&connection_id=5983f850-3d50-4ac3-9c06-d9e0fdaf7212' from origin 'http://local.site.test' has been blocked by CORS policy: Request header field x-csrf-token is not allowed by Access-Control-Allow-Headers in preflight response.
Everything is working on the backend, even the equivalent calls.

Based on the error you are receiving, it looks like you are including your CSRF token to all your AJAX requests. Stream API servers have a whitelist of headers that you can pass, this is to safe developers from sending sensitive data by accident. In this specific case it is arguable that csrf-token could be in such whitelist for the sake of ease of use.
Perhaps you are using something like this on your frontend?
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
If that's the case my suggestion is to opt for a more fine grained solution such as:
$.ajaxSetup({
url: "/laravel/",
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
Or make sure that only your Laravel backend receives the CSRF token by extracting JS code doing Ajax calls.
CSRF tokens are not as valuable as session IDs but they exist to make your application more secure and are not meant to be shared with 3rd parties.

Related

PHP CloudFront Proxy / Forward Requests For Video Streams Via API

I'm trying to forward a request made from the client for a stream, where it keeps the requests originally made from the video player intact:
Content-Type: Keep-Alive;
Range: 0-
...
What I'm Using:
Frontend: Web - ReactJS
Backend: PHP REST API
CDN: AWS CloudFront
Storage: AWS S3
Architecture Graphic
Reason:
I need to be able to authenticate the user with our own JWT middleware through the REST to validate if they can access the file.
Constraints:
Cannot use nginx to forward the request, unless there is still a way to authenticate it with the PHP Middleware.
What I've Looked Into:
aws php sdk
I've look at the AWS PHP, but the documentation on this specific functionality seems to be missing.
guzzle + php curl
I'm afraid my knowledge is lacking in terms of what I would need to pass onto the CloudFront for this to work.
cloudfront signed url/signature
Unless I'm mistaken, this would not be helpful because the video expiration for access would be set by AWS and not by the App's REST API, so if they refresh their JWT it would not be updated with the signature.
why not s3 directly?
S3 doesn't support headers for chunks like, Range: 0-100 bytes.
Any help or recommendations would be appreciated, even if it means recommending to buy something pre-built to look at how they implemented it.
======= UPDATE: June 29, 2020 =======
After the recommendation from #ChrisWilliams, I ended up creating a script on AWS Lambda#Edge with the following configurations:
Trigger: CloudFront - viewer request
The reason for viewer request was because it's the only way to get the GET query parameters from the user's original request.
Function Code:
(Please forgive the very rough code to get things working)
File: index.js
// IMPORTS
const zlib = require('zlib');
const https = require('https');
// HTML ERROR TEMPLATE
const content = `
<\!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Unauthorized Page</title>
</head>
<body>
<p>Unauthorized!</p>
</body>
</html>
`;
// TRIGGER FUNCTION
exports.handler = async (event, context, callback) => {
// Getting request and response
const originalResponse = event.Records[0].cf.response;
const request = event.Records[0].cf.request;
// Setup for html page (cont content)
const buffer = zlib.gzipSync(content);
const base64EncodedBody = buffer.toString('base64');
// Response Templates
var response401 = {
headers: {
'content-type': [{key:'Content-Type', value: 'text/html; charset=utf-8'}],
'content-encoding' : [{key:'Content-Encoding', value: 'gzip'}]
},
body: base64EncodedBody,
bodyEncoding: 'base64',
status: '401',
statusDescription: "OK"
};
var response500 = {
headers: {
'content-type': [{key:'Content-Type', value: 'text/html; charset=utf-8'}],
'content-encoding' : [{key:'Content-Encoding', value: 'gzip'}]
},
body: base64EncodedBody,
bodyEncoding: 'base64',
status: '500',
statusDescription: "OK"
};
// Perform Http Request
const response = await new Promise((resolve, reject) => {
// Expected ?token=ey...
const req = https.get(`https://myauthserver.com/?${(request && request.querystring) || ''}`, function(res) {
if (res.statusCode !== 200) {
return reject(response401);
}
return resolve({
status: '200'
});
});
req.on('error', (e) => {
reject(response500);
});
}).catch(error => error);
// Get results from promise
const results = await response;
if (results.status === '200') {
// Successful request - continue with the rest of the process
callback(null, request);
}
// Not successful, show the errors results (401 or 500)
callback(null, results);
};
NOTE: You will have to try this a few times in case any typos or syntax errors arise because of the caching. I also recommend trying this with different IP addresses to validate access to the content. Not to mention you will get scenarios of 502 if the returned request isn't formatted correctly with the base64EncodedBody.
DOUBLE NOTE: This was after looking at the tutorials from AWS that weren't working or outdated and looking at the comments of multiple devs not getting things working.
I would suggest using a Lambda#Edge function rather than adding a third stage in front of your CloudFront.
By adding a proxy in front of your CloudFront it could lead to issues with debug, and allows someone to bypass the proxy to reach your CloudFront origin without locking it down.
Using a Lambda#Edge function guarantees that the solution validates the authenticity of the JWT token, it could be configured to either validate the JWT token with the Lambda function directly or have the Lambda call an endpoint you build to validate. If the JWT is invalid it can reject the request.
Amazon have a great article with a demo stack that demonstrates how you can make use of this.

Request to api using jquery ajax with php

hi guys i'm getting stuck in my code i'm requesting to a php api to get data with using jquery ajax. please help me with a solution
Ajax code
$.ajax({
url: request_url+"courses.php",
type: 'POST',
dataType: 'jsonp',
cors: true ,
contentType:'application/json',
data: { request_courses: courses },
secure: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
beforeSend: function (xhr) {
xhr.setRequestHeader ("Authorization", "Basic " + btoa(""));
},
success: function (data){
console.log("helo "+data);
}
});
Php Api code
if(isset($_POST['request_courses'])){
$courses = mysqli_query($conn, "SELECT * FROM `courses`");
while ($rows = mysqli_fetch_assoc($courses)) {
$data[] = $rows;
}
echo json_encode(array('status' => 1,'message'=>'responce_courses','data'=>$data));
}
Error While runing this code
courses.html:1 A cookie associated with a cross-site resource at http://localhost/ was set
without the `SameSite` attribute. A future release of Chrome will only deliver cookies with
cross-site requests if they are set with `SameSite=None` and `Secure`. You can review
cookies in developer tools under Application>Storage>Cookies and see more details at
https://www.chromestatus.com/feature/5088147346030592 and
https://www.chromestatus.com/feature/5633521622188032.
Access to XMLHttpRequest at 'file:///home/punkaj/Music/Cordova/ithub/www/index.html' from
origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for
protocol schemes: http, data, chrome, chrome-extension, https.
jquery.js:2 GET file:///home/punkaj/Music/Cordova/ithub/www/index.html net::ERR_FAILED
send # jquery.js:2
ajax # jquery.js:2
h # plugins.js:45
i # plugins.js:45
dispatch # jquery.js:2
y.handle # jquery.js:2
First off, are you sure of the file URLs? 'file:///home/punkaj/Music/Cordova/ithub/www/index.html' seems a bit sketchy IMO. Make sure that it's correct.
And secondly, looks like the AJAX referenced file (courses.php) is trying to set a new cookie on the browser, which is a bad practice in REST APIs. It's better to just return/output whatever value you want, and when you have that value in your 'success' AJAX function, you can save it there as a cookie back in the original script.
Lastly, it looks like you're getting the file directly by using an FTP protocol (file:///) rather than an HTTP protocol (http://) to get the content of the HTML file. Can't provide any further assistance on that without seeing the source code

JQuery AJAX cross-site request under Laravel CSRF protection

I'm building a CMS-like web application using Laravel(back-end) and ReactJS with JQuery(front-end).
I decide to put the existing Web API into a separate domain(api.test.com), and my user interface is on the different domain(test.com).
On test.com, I launch an ajax request to api.test.com to modify some resource on the server:
$.ajax({
url: "api.test.com",
method: 'POST',
data: {...}
success: function (no) {
// ...
}
});
And of course it's illegal due to security problem. However, I can configure my web server:
For Nginx:
add_header Access-Control-Allow-Origin http://test.com;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE;
add_header Access-Control-Allow-Headers X-Requested-With,X-CSRF-TOKEN,X-XSRF-TOKEN;
The Access-Control-Allow-Origin problem is solved perfectly but another problem occurs due to Laravel's CSRF protection...
Laravel requires a CSRF token included in the request(POST,PUT...which will modify the resource) by default.
In my case, I must generate csrf_token on api.test.com rather than test.com because different domain do not share the token.
I followed the User Guide of Laravel and added these code to my front-end:
$.ajax({
url: "api.test.com/token", // simply return csrf_token();
method: "GET",
success: function (token) {
// Now I get the token
_token = token;
}.bind(this)
});
and modify the previous request implementation:
$.ajax({
url: "api.test.com",
method: 'POST',
headers: {
"X-CSRF-TOKEN": _token // Here I passed the token
},
data: {...}
success: function (no) {
// ...
}
});
But Laravel responses a status code of 500. Then I checked the VerifyCsrfToken.php:
protected function tokensMatch($request)
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
// Log::info($request->session()->token() . " == $token");
return Str::equals($request->session()->token(), $token);
}
The $token which I 'POST' to is different from what it was ($request->session()->token()).
I found that the validation tokens on server are different when calling $.ajax.
I try to put the two requests in the same session(by changing the cookie), but it's impossible.
I spent a lot of time to solve this problem but didn't work it out.
Have any idea or solution?
Thanks,
Micooz
Thank you for answering my question. I've considered disabling the CSRF protection to some URIs but I don't want to take these risk.
The key point of my question is that the $.ajax forgets carrying cookies before request, and resulting token validation failed.
Now I setup the JQuery Ajax, let it carry cookies before make a request.
$.ajaxSetup({
xhrFields: { withCredentials: true }
});
and Nginx conf:
add_header Access-Control-Allow-Credentials true;
BTW, it's not necessary to include the token in the data:{}(Form).
All the problems are settled and it works perfectly for me.
Laravel expects the token as a data variable, included on your fields, the name of the var needs to be _token try to change it.
Another solution is including the token in your data not in the headers.
$.ajax({
url: "api.test.com",
method: 'POST',
data: { _token : _token }
success: function (no) {
// ...
}
});
You can follow this url
http://laravel.io/forum/11-14-2014-disabling-the-csrf-middleware-in-laravel-5
In this link, you need to wrap up VerifyCsrfToken class with new one class where you specify actions on which you want not use csrf_token

Laravel csrf token mismatch for ajax GET Request

I have an application that is using angular js to make an $http GET request to a server. One page particularly has a form which gets a csrf token embedded into it as follows
<input type="hidden" ng-model="token" value="{{{ Session::getToken() }}}">
In my controller I have the following code:
public function getMethod($arg, $token)
{
/*Check for csrf token validity*/
if ($token != Session::token()) {
return Response::json(
array(),
403
);
}
........
}
From the client side I make a request like this:
var arg = $scope.arg;
var get_url = '/url/routing/to/controller/arg/' + arg + '/' + $scope.token;
$http({method: 'GET', url: get_url})
.success(function(data, status, headers, config) {
//Do stuff after success
.............
})
.error(function(data, status, headers, config) {
//Handle error
.......
});
I am not exactly sure how the GET request can be integrated with csrf tokens but when I make a GET request to the registered URL, I get a token mismatch. Basically a new token is generated every time an ajax request is sent to the server, therefore the initial token extracted in the form input element does not match when I am comparing it in the controller. Could anyone tell me how csrf validity can be done in this case?
Thanks
You should not be adding/modifying resources through GET, therefore you do not need a token on a get request. CSRF tokens are needed only in methods that change or add resources to your application using the currently logged in user's credentials.

Creating a restful API using PHP as server and jQuery as client

I'm trying to create a Javascript client API service which calls the API of my site. This will be cross domain and i'm aware of the problems this causes. However, I need the user to send through some user credentials (whether that be their username and password encoded obviously or an API key + secret) so that I can return user specific details.
I initially looked at using the jsonp datatype however this doesnt allow you to set any custom headers so ruled this out.
I've been searching the web for a while and been unable to find a secure way of doing this cross domain, has anyone had any success with this and can give me some advice?
UPDATE:
I've tried this following code as suggested by lu1s, however I get an alert of 'boo' as stated n the error function..
$.ajax({
url: 'http://www.dotsandboxes.co.cc/__tests/cors.php',
type: 'GET',
dataType: 'json',
success: function() { alert('hello!'); },
error: function() { alert('boo!'); },
beforeSend: function (xhr) {
xhr.setRequestHeader('securityCode', 'Foo');
xhr.setRequestHeader('passkey', 'Bar');
}
});
Thanks
You can. Try adding the Allow-Access-Control-Origin: * to your HTTP response headers, as well as the correct content-type.
Try with a simple PHP script like this:
<?php
header('Access-Control-Allow-Origin: *');
header('Content-type: text/json');
echo json_encode(array('success'=>true,'data'=>'foobar'));
?>
Check this site to read more info about cross-origin: http://enable-cors.org/
About the authentication, it's NOT recommended to send usernames or passwords, even if they're encrypted. As you stated, it's better to pass a token in the URL. Best if following standards like http://oauth.net/2/ .

Categories