Laravel Sanctum with SPA: Best practice for setting the CSRF cookie - laravel

Currently, my SPA is simple and has only a contact form. The form data is sent to the backend. I have installed Laravel Sanctum for that.
axios.get('/sanctum/csrf-cookie').then((response) => {
axios.post('api/contact')
.then((response) => {
//doing stuff
});
});
This works perfectly fine. However, I was wondering. Where and which time in your SPA do you fire the initial request to get the CSRF cookie? (axios.get('/sanctum/csrf-cookie'))
My first thoughts were:
Every time the page is mounted/loaded
Only when you receive a 419 and you attempt to refresh the token and retry the previous request
You don't fire any API requests, only when the user tries to log in and only then you are requesting a cookie (in my case I don't have any user authentification)
For each API request, you wrap it with axios.get('/sanctum/csrf-cookie') around

So for future readers, I chose:
Only when you receive a 419 and you attempt to refresh the token and retry the previous request
For that, I'm using the package axios-auth-refresh.
My settings look like that
//bootstrap.js
import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
axios.defaults.withCredentials = true;
axios.defaults.baseURL = process.env.GRIDSOME_BACKEND_URL;
const refreshAuthLogic = (failedRequest) => axios.get('/sanctum/csrf-cookie').then((response) => Promise.resolve());
createAuthRefreshInterceptor(axios, refreshAuthLogic, { statusCodes: [419] });
window.axios = axios;

Related

Axios not sending XSRF token with headers

I am integrating a Vue JS (Quasar framework) based SPA with API. The API is built in Laravel and is using sanctum for CSRF.
When I send a request to the sanctum endpoint https://someweburl.com/sanctum/csrf-cookie it sends the XSRF-TOKEN as cookie correctly. But when I am sending the the POST request, the X-XSRF-TOKEN is not attaching itself with the header. And I am getting a 'token mismatch' error.
The front-end is on my localhost:8080 while the API is live on a url. I do not have direct access to the Laravel project but only the API.
Following is my axios configuration
import Vue from 'vue';
import axios from 'axios';
Vue.prototype.$axios = axios;
const apiBaseUrl = "https://someweburl.com";
const api = axios.create({ baseURL: apiBaseUrl });
api.defaults.withCredentials = true;
Vue.prototype.$api = api;
Vue.prototype.$apiBaseURL = apiBaseUrl;
export { axios, api, apiBaseUrl }
Following is the request format that I am trying to achieve i.e A POST request after getting the CSRF
export const fetchAllEvents = async function (context, payload) {
this._vm.$api.get('/sanctum/csrf-cookie').then(response => {
this._vm.$api.post('/api/website/event/all').then(response => {
context.commit('setAllEvents', response.data.data);
}).catch(error => {
console.log(error);
})
}).catch(error => {
console.log(error);
})
}
When I check use Postman to make the POST request with X-XSRF-TOKEN added as header, i am getting the correct response. Which means the API is working correctly. But there's some issue with axios.
Any help will be appreciated.
Maybe that is because the Axios only sets the X-XSRF-TOKEN header when the front and backend have the exact origin. You mentioned you are trying with your frontend app at your localhost and the API is on the web at another URL.
See the axios GitHub repository for more information: https://github.com/axios/axios/blob/cd8989a987f994c79148bb0cacfca3379ca27414/lib/adapters/xhr.js#L179

I'm getting "blocked by CORS policy" when I try to call Instagram API using Axios [duplicate]

This question already has answers here:
Access-Control-Allow-Origin with instagram api
(1 answer)
CORS error, when i use instagram API with angularjs
(1 answer)
Closed 3 years ago.
I'm trying to fetch some images from my Instagram account in a Laravel application with Vue as front end. When I try to do it in a standalone Vue app, it works well, but when I do so with Laravel, I got a message saying "has been blocked by CORS policy: Request header field x-csrf-token is not allowed by Access-Control-Allow-Headers in preflight response."
I'm using Laravel 5.8 and the Vue and Axios that comes within in and I'm using Homestead as my localhost server.
I've tried a lot of tips that I found here and on Google but I had no success. Basically, I'm trying the very basic of Axios call
beforeMount() {
axios.get('https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]').then(response => console.log(response))
}
I already created a Cors middleware on Laravel and tried a lot of headers settings on Axios.
I'm basically trying to retrieve a list of my Instagram posts and bypass that cors / x-csrf error.
Laravel automatically applies the X-CSRF-TOKEN header to all axios requests. This is so you can communicate with your application without having to pass the CSRF token every time for POST, PUT, DELETE, etc.
resources/js/bootstrap.js (default settings)
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
You should be able to remove the offending header by doing something like this:
beforeMount() {
// create a new instance so we don't delete the csrf token for other requests
let instance = axios.create();
// delete the x-csrf-token header
delete instance.defaults.headers.common['X-CSRF-TOKEN'];
// use the new instance to make your get request
instance.get('https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]')
.then(response => console.log(response))
}
Your AJAX request to the Instagram API endpoint has to be sent as a jsonp request which means the dataType of the request has to be jsonp.
This blob in axios repository contains an example of sending a request using jsonp which is mentioned below.
Install jsonp package, if you haven't already.
npm install jsonp --save
and then;
const jsonp = require('jsonp');
jsonp('http://www.example.com/foo', null, (err, data) => {
if (err) {
console.error(err.message);
} else {
console.log(data);
}
});
Below is an example of sending a request using jQuery method with jsonp dataType to the Instagram API endpoint.
$.ajax({
url: "https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]",
type: "GET",
crossDomain: true,
dataType: "jsonp",
success: function(response){
console.log(response);
}
});

Axios : put on hold requests

I have the following axios interceptor.
It checks the validity of the stored token. If not valid, a request is fired to retrieve and store the refreshed token.
axios.interceptors.request.use(async config =>
if(checkValidity(localStorage.getItem('token')) === false) {
const response = await axios.get('http://foobar.com/auth/refresh');
localStorage.setItem('token', response.headers['token']);
config = getConfigFromResponse(response);
}
return config;
});
It works great. The problem is that if I have many requests with invalid token then many requests to http://foobar.com/auth/refresh are done to refresh it.
Is it possible to put all the requests in an array and fire them after when the refresh is done ?
The idea is to avoid catching 401 errors and replaying the request : this is why I want to "save" the requests while the token is being retrieved and then fire them when the token is ready.

Can not get laravel cookie laravel_token while consuming own API

Cookie is avaiable in browser but can not get it even can not check if it exists .
working on laravel and vuejs app where I am using passport for authentication.
I am consuming own api with vuejs so I have added CreateFreshApiToken middleware in kernel.php
vuejs is creating and sending laravel_token cookie successfully but I can not read it using vuejs or javascript.
since I can not check if cookie is available or not I can not check if user is logged in or not.
How I will tell vuejs that user is logged in and take to intended route ?
For now I am saving access_token in localStorage and sending it with each request but I am looking for a solution which can help to use laravel builtin laravel_token cookie to authenticate and authorize the users.
More Details
If i run this command in console then I get response
document.cookie.match('XSRF-TOKEN')
But
document.cookie.match('laravel_token')
returns null
I think the tick icon in the shown image at top is sign of making it private or something that makes it inaccessible.
Update
I found something from internet
But I still want to know how I can tell vuejs app that user is logged in or not ??
Update 2
For now I am sending access_token in header manually from vuejs and it is working fine.
if(localStorage.getItem('laravel_token') !== 'undefined' && !! localStorage.getItem('laravel_token') == true){
window.axios.defaults.headers.common['Authorization'] = 'Bearer '+ localStorage.getItem('laravel_token')
}
A little work around will be for you to implement the authenticated method in your LoginController, and return a json success code. You can then receive this code and store.
public function authenticated(Request $request, $user)
{
// return redirect()->intended($this->redirectPath());
return json_encode(['authenticated' => true]);
}
Using axios...
axios.post('/login', {
email: user.username,
password: user.password
}).then(response => {
.then(response => {
console.log(response)
//response.data.authenticated will return true if user has been authenticated
})
}).catch(err => {
console.log(err)
})

Dealing with axios interceptors when sending many ajax requests

I use Larave+JWT and vue2 + vuex2 + axios
So when user logins I store auth token in vuex store. When the token expires I need to refresh it. In order to refresh it I need to send the same token to /refresh route, and get a new token. At least that's how I got it and actually it works.
The problem is that interceptor catches 401 responses and tries to refresh token, but what if, say, in my component I send many requests with expired token? Since ajax requests are async, the interceptor code runs many times. So I got many refresh requests. Once the initial token is refreshed it is not considered valid. When interceptor tries to refresh invalid token server responds with error and I redirect to login page.
Here is the code:
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
axios.post('auth/refresh').then((response) => {
let token = response.data.token
store.dispatch('auth/setAuthToken', token)
let authorizationHeader = `Bearer ${token}`
axios.defaults.headers = { 'Authorization': authorizationHeader }
originalRequest.headers['Authorization'] = authorizationHeader
return axios(originalRequest)
}, (error) => {
store.dispatch('auth/clearAuthInfo')
router.push({ name: 'login' })
})
}
return Promise.reject(error);
});
I think you'll have to change your approach on how you refresh your token. Leaders like Auth0 recommends proactive periodic refresh to solve this problem.
Here is a SO answer where they talk about it.
Set the token expiration to one week and refresh the token every time the user open the web application and every one hour. If a user doesn't open the application for more than a week, they will have to login again and this is acceptable web application UX.

Resources