Should a Laravel SPA still use a CSRF token for security? - laravel

I ran into multiple difficulties with CSRF when building a small SPA with Laravel and Vue.js:
I use index.html as the only view, the rest is handled by vue-router using single file components (i.e. .vue files)
Because I'm not using PHP or Blade on the front, I can't inject csrf_token() into my view. Even if I did, the token would eventually expire, yet because the app has no (or very few) page refresh(es), it wouldn't know if the token changed, and it would eventually fail to make AJAX requests with the old token
Some answers suggest to pass the token in a cookie and then retrieve it with JS. This approach suffers from the same problem as above -- the SPA is never notified when the token changes
I could dig in the internal workings of Laravel and throw an event every time the token changes; the front-end could use Laravel Echo to listen to the changes, but then the question raises, is it even worth to bother?
Lastly, I was suggested to use JWT; however, as I understand, JWT is used for authentication purposes, while CSRF -- for every single request regardless of the HTTP verb or intent.
With the last two points in mind, do you think it is necessary/advisable to use a CSRF token in a Laravel SPA? And if so, what would be the best implementation (cookie with the token, dedicated route returning the token, or other)? If not, what are the alternatives?

Comments don't have enough space, so I'm adding this as an answer, but this is just a concept as I have extremely low experience with Vue.
From the docs
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
So the concept would be something like this:
Set a custom header from Laravel.
When building/starting up your Vue application, get the custom header and set it somewhere global.
When making a request, intercept it and add the CSRF token from the global storage
axios.interceptors.request.use(function (config) {
// Get your token from the place you stored it and add to the request
});
Intercept the response and store a new token
axios.interceptors.response.use(function (response) {
// Store the new CSRF token in the same place you stored the 1st one.
});
Loop forever

Related

Send object with axios get request [duplicate]

This question already has answers here:
Axios get in url works but with second parameter as object it doesn't
(4 answers)
Closed 5 years ago.
I want to send a get request with an object. The object data will be used on the server to update session data. But the object doesn't seem to be sent correctly, because if I try to send it back to print it out, I just get:
" N; "
I can do it with jQuery like this and it works:
$.get('/mysite/public/api/updatecart', { 'product': this.product }, data => {
console.log(data);
});
The object is sent back from server with laravel like this:
public function updateCart(Request $request){
return serialize($request->product);
The same thing doesn't work with axios:
axios.get('/api/updatecart', { 'product': this.product })
.then(response => {
console.log(response.data);
});
I set a default baseURL with axios so the url is different. It reaches the api endpoint correctly and the function returns what was sent in, which was apparently not the object. I only get "N; " as result.
Axios API is a bit different from the jQuery AJAX one. If you have to pass some params along with GET request, you need to use params property of config object (the second param of .get() method):
axios.get('/api/updatecart', {
params: {
product: this.product
}
}).then(...)
You can pass either a plain object or a URLSearchParams object as params value.
Note that here we're talking about params appended to URL (query params), which is explicitly mentioned in the documentation.
If you want to send something within request body with GET requests, params won't work - and neither will data, as it's only taken into account for PUT, POST, DELETE, and PATCH requests. There're several lengthy discussions about this feature, and here's the telling quote:
Unfortunately, this doesn't seem to be an axios problem. The problem
seems to lie on the http client implementation in the browser
javascript engine.
According to the documentation and the spec XMLHttpRequest ignores the
body of the request in case the method is GET. If you perform a
request in Chrome/Electron with XMLHttpRequest and you try to put a
json body in the send method this just gets ignored.
Using fetch which is the modern replacement for XMLHtppRequest also
seems to fail in Chrome/Electron.
Until it's fixed, the only option one has within a browser is to use POST/PUT requests when data just doesn't fit into that query string. Apparently, that option is only available if corresponding API can be modified.
However, the most prominent case of GET-with-body - ElasticSearch _search API - actually does support both GET and POST; the latter seems to be far less known fact than it should be. Here's the related SO discussion.

Get a new csrf token, even if the current token has expired

Is there a way with Laravel to obtain a new CSRF-token, even when the current token might have expired? I'd like to be able to get a new token without updating the user interface.
I've been experimenting with the Visibility API to check if the browser (only mobile users) gets active again but I soon found out that's not really the issue. If the token has expired, so far I'm unable to get a new one, because I get the token mismatch exception. I want a new token, ignore any mismatch exception, put it in the DOM and let the user continue again.
My user might experience a long idle time but when he/she reactivates the mobile page I want the user to be able to proceed where he/she left off without the need to let them reload the page and provide initial data (like name) again.
You can use this script on pages needed a token refresh without refresh page :
<script type="text/javascript">
var csrfToken = $('[name="csrf_token"]').attr('content');
setInterval(refreshToken, 3600000); // 1 hour
function refreshToken(){
$.get('refresh-csrf').done(function(data){
csrfToken = data; // the new token
});
}
setInterval(refreshToken, 3600000); // 1 hour
</script>
And in your routes/web.php
Route::get('refresh-csrf', function(){
return csrf_token();
});
You can also disable csrf_token verifiction on certain routes, creating a middleware, example below :
https://laracasts.com/discuss/channels/general-discussion/l5-disable-csrf-middleware-on-certain-routes

OKTA Session API

Is there an API (we are using the OKTA Sign In Widget) to get the original res.idToken?
Reason I ask is that users might hit our site after logging in to a different site and we need the idToken. We can tell if the session exists of course..
oktaSignIn.session.exists((exists) => {
if (exists) { oktaSignIn.session.get((res) =>
But I don't see an idToken in there.
Thanks!
Can you use tokenManager to store the tokens?
After receiving the id token, you can add it a tokenManager. The token can later be retrieved from there.
Refer - https://github.com/okta/okta-signin-widget#oidc-tokenmanageraddkey-token
Well.. seems like I can get a new IDToken. The docs say this:
oktaSignIn.idToken.refresh(token, function (newToken) {
// New id_token with extended lifetime
});
My problem of course was that I did not have have a token to refresh. Turns out you can just do... (use null instead of a token)
oktaSignIn.idToken.refresh(null, function (newToken) {
// New id_token with extended lifetime
});
Hopefully this is not a bug but a feature :-)

Yii Ajax request CSRF can not be verified

I use a few different AJAX calls in one of my pages through a couple of different methods.
CHtml::link()
CHtml::ajax()
Within CGridview
Since enabling CSRF I'm having difficulty verifying the token. What is the correct way? I've read a few posts, but struggling to implement. For instance in CHtml::link() I've tried:
'data' => "Yii::app()->request->csrfTokenName = Yii::app()->request->csrfToken",
and also within CGridview:
data: {
Yii::app()->request->csrfTokenName => Yii::app()->request->csrfToken
},
1- Fot ajax you have ajax, ajaxLink and ajaxButton not just link.
2- CSRF token works when you use POST request
3- Add CSRF token like this:'data'=>array('YII_CSRF_TOKEN' => Yii::app()->request->csrfToken)`

Ajax security: how to be sure that data sent back to the server is generated by my code?

I apologize in advance if this question sounds naive to you.
The problem is this: I have this function and I want the callback function to send the "response" back to my server via Ajax.
function FbInviteFriends()
{
FB.ui({
method: 'apprequests',
message: 'Hi! Join me on XXXXXXX'
},
//My callback function
function(response){
//Send response to my server
}
Is there a way to check that the response I'm going to receive server-side is actually the same I got when the callback function is called and that the response hasn't been modified on the client-side by the user?
Thanks!
There's a few ways, but all of them fall on the same principle - you can never know for sure, so treat it with a grain of salt and validate.
That said, one way to put at least one usage constraint may look like this:
Page accessed: Generate a token GUID. Render it at the client.
Store in the user session the moment it was created/used, together with user profile.
Client appends the token to all Ajax posts.
Token is validated at the server; must match SessionID, user profile (if any), and maximum usage timeout.
If it fails validation, abort the operation.

Resources