I'm making a post request using Axios and this call returns data in the response headers and body. In the headers, it's returning an x-auth-token and I want to get the value of this token but it returns:
undefined is not an object
Here is how I'm doing it:
axios.post('app.com/api/login', data)
.then(response => {
console.log(response.headers.get("x-auth-token"));
})
.catch(error => {
console.log(error)
});
You need to parse your response first.
axios
.post('app.com/api/login', data)
.then(response => response.json())
.then(response => {
console.log(response.headers.get("x-auth-token"));
})
.catch(error => {
console.log(error)
});
After that, In second then you can log the whole response and find where your x-auth-token resides.
In the Github comment, it's clearly mentioned how to retrieve the headers
see
fetchFromServer = async(data) => {
const response = await axios.post(url, data, headers)
console.log(response.headers)
}
If you could see all the headers in your log you can try either of these to get the data from the response. To check the keys available in your response you can try
console.log(Object.keys(response.headers))
console.log(response.headers.your_required_key (For example response.headers.token)
console.log(response.headers["your_required_key"] if the above fails. (console.log(response.headers["content-type"])
Related
I am using Vuejs SPA with Laravel API as backend. I successfully got the personal access token and store in localStorage and Vuex state like below.
token: localStorage.getItem('token') || '',
expiresAt: localStorage.getItem('expiresAt') || '',
I use the access token every time I send axios request to laravel api. Every thing works well. However, initially the token was set to 1 year expiration so when I develop I didn't care about token being expired and today suddenly I thought what is going to happen if token expired. So I set token expiry to 10 seconds in laravel AuthServiceProvier.php.
Passport::personalAccessTokensExpireIn(Carbon::now()->addSecond(10));
and then I logged in and after 10 seconds, every requests stopped working because the token was expired and got 401 unauthorised error.
In this case, how can I know if the token is expired? I would like to redirect the user to login page if token is expired when the user is using the website.
Be as user friendly as possible. Rather than waiting until the token expires, receiving a 401 error response, and then redirecting, set up a token verification check on the mounted hook of your main SPA instance and have it make a ajax call to e.g. /validatePersonalToken on the server, then do something like this in your routes or controller.
Route::get('/validatePersonalToken', function () {
return ['message' => 'is valid'];
})->middleware('auth:api');
This should return "error": "Unauthenticated" if the token is not valid. This way the user will be directed to authenticate before continuing to use the app and submitting data and then potentially losing work (like submitting a form) which is not very user friendly.
You could potentially do this on a component by component basis rather than the main instance by using a Vue Mixin. This would work better for very short lived tokens that might expire while the app is being used. Put the check in the mounted() hook of the mixin and then use that mixin in any component that makes api calls so that the check is run when that component is mounted. https://v2.vuejs.org/v2/guide/mixins.html
This is what I do. Axios will throw error if the response code is 4xx or 5xx, and then I add an if to check if response status is 401, then redirect to login page.
export default {
methods: {
loadData () {
axios
.request({
method: 'get',
url: 'https://mysite/api/route',
})
.then(response => {
// assign response.data to a variable
})
.catch(error => {
if (error.response.status === 401) {
this.$router.replace({name: 'login'})
}
})
}
}
}
But if you do it like this, you have to copy paste the catch on all axios call inside your programs.
The way I did it is to put the code above to a javascript files api.js, import the class to main.js, and assign it to Vue.prototype.$api
import api from './api'
Object.defineProperty(Vue.prototype, '$api', { value: api })
So that in my component, I just call the axios like this.
this.$api.GET(url, params)
.then(response => {
// do something
})
The error is handled on api.js.
This is my full api.js
import Vue from 'vue'
import axios from 'axios'
import router from '#/router'
let config = {
baseURL : process.env.VUE_APP_BASE_API,
timeout : 30000,
headers : {
Accept : 'application/json',
'Content-Type' : 'application/json',
},
}
const GET = (url, params) => REQUEST({ method: 'get', url, params })
const POST = (url, data) => REQUEST({ method: 'post', url, data })
const PUT = (url, data) => REQUEST({ method: 'put', url, data })
const PATCH = (url, data) => REQUEST({ method: 'patch', url, data })
const DELETE = url => REQUEST({ method: 'delete', url })
const REQUEST = conf => {
conf = { ...conf, ...config }
conf = setAccessTokenHeader(conf)
return new Promise((resolve, reject) => {
axios
.request(conf)
.then(response => {
resolve(response.data)
})
.catch(error => {
outputError(error)
reject(error)
})
})
}
function setAccessTokenHeader (config) {
const access_token = Vue.cookie.get('access_token')
if (access_token) {
config.headers.Authorization = 'Bearer ' + access_token
}
return config
}
/* https://github.com/axios/axios#handling-errors */
function outputError (error) {
if (error.response) {
/**
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
if (error.response.status === 401) {
router.replace({ name: 'login' })
return
}
else {
/* other response status such as 403, 404, 422, etc */
}
}
else if (error.request) {
/**
* The request was made but no response was received
* `error.request` is an instance of XMLHttpRequest in the browser
* and an instance of http.ClientRequest in node.js
*/
}
else {
/* Something happened in setting up the request that triggered an Error */
}
}
export default {
GET,
POST,
DELETE,
PUT,
PATCH,
REQUEST,
}
You could use an interceptor with axios. Catch the 401s and clear the local storage when you do then redirect user to appropriate page.
I am creating new frontend for an interview system. Some its API endpoints is updated, so getting pagination info is not a problem, but old ones still have pagination data inside response headers.
P.S. we are using react, redux and redux-observable
RxJS has the following call:
ajax({ ...params }).pipe(
map(response => {
// here I need to somehow get headers from ajax response
}),
catchError(errorResponse => {
// return error
})
)
I've been looking for the same answer, looks like there is a way (See: https://stackblitz.com/edit/typescript-k2ggm2?file=index.ts):
ajax({ ...params }).pipe(
map(response => {
// here I need to somehow get headers from ajax response
console.log(response.xhr.getAllResponseHeaders())
console.log(response.xhr.getResponseHeader('pragma'))
}),
catchError(errorResponse => {
// return error
})
)
I am wondering if it is possible to do in fetch all the things you can do in traditional ajax?
Because I'm having a problem with a simple login authentication using express. I want to send a response like Login error if the username/password is incorrect, or to redirect the user to the homepage if both is correct, to the client without refreshing the page.
I understand that you can do this in AJAX, but is it possible to do it in fetch also?
I tried using express js and sending a response through a json, but I can't figure out how to handle the response without refreshing the page.
I tried doing it like this in the express server
//if valid
res.json({
isValid: true
})
//if invalid
res.json({
isValid: false
})
And in the client side, specifically in the login page, I have this javascript that handles the submitting of the information
fetch('https://localhost:3000/auth', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username,
password
})
})
.then(response => response.json())
.then(data => {
//I understand that in this part, you can handle the response, but the problem is, I don't know how.
}
})
.catch(console.log)
You are SO close! You've got the fetch, then you've parsed it with response.json, so the next thing is the .then(). In that, you have the JSON object being passed into a param you've named data. All you need to do is check if that has the isValid property!
fetch('https://localhost:3000/auth', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username,
password
})
})
.then(response => response.json())
.then(data => {
if(data.isValid){
// Do something with a valid user. Redirect or whatever.
} else {
// Here, isValid is not set, or is false.
// Send them packing!
}
}
})
.catch(err => console.error("I died: ", err) );
ALSO, take a look at the .catch() block -- in the event of an error, that catches an Error thrown by either the fetch(), or a then(). So you need to add a parameter for the error, and a function body to handle that. I've edited my code sample to demonstrate.
Won't actually run here, but it's formatted all pretty.
I have the following request to my laravel endpoint:
axios.get('http://localhost:8000/auth/login', {})
.then(function (response) {
console.log(response);
return {};
})
.catch(function (error) {
return {}
});
And my laravel endpoint set up as:
public function index() {
var_dump('login called.');die;
return response()->json(
[],
200
);
}
I Started my nextjs server (port 3000) and laravel server(8000), and when i browse to localhost:8000/auth/login in my browser I see "login called". however when I do that axios call, I get a status 200ok but no response data.
Request URL:http://localhost:8000/auth/login
Request Method:GET
Status Code:200 OK
Remote Address:127.0.0.1:8000
Referrer Policy:no-referrer-when-downgrade
Any idea what I am doing wrong?
Nothing is wrong with your code you are getting the response correctly, you see "login called" because you are accessing from a browser, therefore a browser has the cappability to render the html and you can see that.
But that axios call expects some json in return.
If you tweak the response a bit:
public function index() {
return response()->json(
['data' =>'Log in called'],
200
);
}
and if you twak axios response a bit
axios.get('http://localhost:8000/auth/login', {})
.then(function (response) {
console.log(response.data);
return {};
})
.catch(function (error) {
return {}
});
Inspect element open console and you will see 'Log in called'
I'm trying to get sinon.fakeServer to make axios return a faked response. Instead of returning the mocked payload, I can see the network request 404s or does a timeout trying to go to the actual URL.
My setup:
describe('test call', () => {
var server;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondWith(
"https://my.domain.com/myresource",
[200, { "Content-Type": "application/json" }, "[]"]
);
server.autoRespond = true
});
it('returns empty array', done => {
axios
.get('https://my.domain.com/myresource')
.then(res => {
expect(true).to.equal(true);
done()
})
.catch(err=>{
console.log(err.message);
expect(false).to.equal(true);
done();
});
});
afterEach(() => {
server.restore();
});
})
It seems that your execution environment is NodeJS, even though it's not mentioned. Others had the same issue - have a look here.
Also the Sinon team mentions that it's outside their scope since XHR are supposed to work correctly in the browser, where their fake server works as expected as it stubs the XHR object.
Axios is using a different library for making requests when running on the server, so this scenario cannot work by default. There are specific mocking libs for axios like moxios as an alternative.