I have a vue web app that uses axios to communicate with an API. The authentication is handled by the server, and not by my app. That is, the server ensures that the user cannot see the app before they have authenticated.
Of course, after some time the user's authentication token expires and my app only notices this when it fires off a get/post request to the API. When this happens the axios request returns a redirect to a login page that, when printed to the console, looks something like this:
config: Object { url: "https://...url for my request...",
method: "get", baseURL: "...base url for api", … }
data: "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<HTML>\n<HEAD>\n<TITLE>Need Authentication</TITLE>\n<link rel=\"stylesheet\" href=\"/Steely.css\" type=\"text/css\">\n</HEAD>\n<BODY>....</BODY>\n</HTML>\n"
headers: Object {
connection: "Keep-Alive",
"content-encoding": "gzip", "content-length": "1686", …
}
request: XMLHttpRequest {
readyState: 4, timeout: 0, withCredentials: false, …
}
status: 200
statusText: "OK"
<prototype>: Object { … }
app~d0ae3f07.235327a9.js:1:97292
What is the best way to redirect the user to this login page and then resume my original request? At the moment I am not even succeeding in recognising this. My axios code tries, and fails, to recognise when this happens and then redirect to user a vue component that has a login page. The relevant part of code looks like this:
export default new class MyAPI {
constructor() {
this.axios = axios.create({
headers: {
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json',
},
baseURL: `https://.../api`,
});
}
// send a get request to the API
GET(command) {
return new Promise((resolve, reject) => {
this.axios.get(command)
.then((response) => {
if (response && response.status === 200) {
if ( response.data && typeof response.data == 'string' && response.data.includes('Require authentication') ) {
store.dispatch('authenticate', this.baseURL+'/'+command).then( (resp) => resolve(resp.data) )
} else {
resolve(response.data);
}
} else {
reject(response.data);
}
})
.catch((err) => { reject('Internal error'+err); });
});
}
}
This results in the dreaded
Internal errorTypeError: e(...) is undefined
error, although this error is almost certainly triggered further down the code since I not recognising the login authentication request.
Is anyone able to recommend how best to recognise and process the login request?
Related
I'm working through this tutorial on creating an app that uses the Spotify API. Everything was going great until I got to the callback portion of authenticating using the authentication code flow.
(I do have my callback URL registered in my Spotify app.)
As far as I can tell, my code matches the callback route that this tutorial and others use. Significantly, the http library is axios. Here's the callback method:
app.get("/callback", (req, res) => {
const code = req.query.code || null;
const usp = new URLSearchParams({
code: code,
redirect_uri: REDIRECT_URI,
grant_type: "authorization_code",
});
axios({
method: "post",
url: "https://accounts.spotify.com/api/token",
data: usp,
headers: {
"content-type": "application/x-www-form-urlencoded",
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
},
})
.then(response => {
console.log(response.status); // logs 200
console.log(response.data); // logs encoded strings
if (response.status === 200) {
res.send(JSON.stringify(response.data))
} else {
res.send(response);
}
})
.catch((error) => {
res.send(error);
});
Though the response code is 200, here's a sample of what is getting returned in response.data: "\u001f�\b\u0000\u0000\u0000\u0000\u0000\u0000\u0003E�˒�0\u0000Ee�uS\u0015��\u000e�(\b\u0012h\u0005tC%\u0010\u0014T\u001e�����0��^:���p\u0014Ѻ\u000e��Is�7�:��\u0015l��ᑰ�g�����\u0"
It looks like it's encoded, but I don't know how (I tried base-64 unencoding) or why it isn't just coming back as regular JSON. This isn't just preventing me logging it to the console - I also can't access the fields I expect there to be in the response body, like access_token. Is there some argument I can pass to axios to say 'this should be json?'
Interestingly, if I use the npm 'request' package instead of axios, and pass the 'json: true' argument to it, I'm getting a valid token that I can print out and view as a regular old string. Below is code that works. But I'd really like to understand why my axios method doesn't.
app.get('/callback', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
const code = req.query.code || null;
const state = req.query.state || null;
const storedState = req.cookies ? req.cookies[stateKey] : null;
res.clearCookie(stateKey);
const authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code',
},
headers: {
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
},
json: true,
};
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
const access_token = body.access_token;
const refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { Authorization: 'Bearer ' + access_token },
json: true,
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect('/#' + querystring.stringify({
access_token: access_token,
refresh_token: refresh_token,
}));
} else {
res.redirect(`/#${querystring.stringify({ error: 'invalid_token' })}`);
}
});
});
You need to add Accept-Encoding with application/json in axios.post header.
The default of it is gzip
headers: {
"content-type": "application/x-www-form-urlencoded",
'Accept-Encoding': 'application/json'
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
}
I have a vuejs app using axios for http requests.
The authorization header is set via a request interceptor like so :
const api = axios.create({
baseURL: process.env.API_URL,
crossdomain: true,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
api.interceptors.request.use(
function (config) {
if (config.url !== "/register") {
const accessToken = localStorage.getItem('token');
if (accessToken) {
config.headers.Authorization = "Bearer " + accessToken;
}
}
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
All the requests made in the app go through this interceptor. But there is a request for which the authorization header is not set for some ios devices (it works fine on web/android devices and some ios devices). Here is the request :
export function getSessions(context, payload) {
return new Promise((resolve, reject) => {
api.get("/sessions/" + payload.sportId + "/?begin=" + payload.period.from + "&end=" + payload.period.to)
.then(({ data }) => {
resolve(data);
})
.catch((error) => {
reject(error);
});
});
}
I don't understand what could be going wrong. All other requests work fine one the devices for which this one doesn't.
I found the reason of the bug : there was a / before the query (/?begin=). The problem occurred on iOS 14.5, 14.7 and maybe other versions but on any other device there was no error, sometimes a 301 http code was answered but that's it.
Postman is not sending request with pm.sendRequest if cookie jar is empty.
I run a Test after every request in a collection, to check if the request was authorized. If not i get the necessary cookie and set it.
This works if the cookie is invalid but already exists in the cookie jar.
If the cookie does not exists already the code runs like expected but pm.sendRequest is not execute at all.
No log inside the request function in the console (i use logs to check if the code part is reached), no request visible in the console. But the other logs are still printed. Neither do i get an error.
The code:
if (pm.response.status == "Unauthorized") {
var domain = pm.environment.get("domain");
const jar = pm.cookies.jar();
jar.unset(domain, "session");
const postRequest = {
url: 'http://' + domain + ':5000/api/v1/oauth2/token',
method: 'POST',
header: {
'Content-Type': 'application/json',
},
body: {
mode: 'raw',
raw: JSON.stringify({
"identification": "admin",
"password": "admin"
})
}
};
console.log("before sending") //**guard one - reached every time**
pm.sendRequest(postRequest, (error, response) => {
console.log("sending") //**guard two - only reached if cookie exists but is invalid**
if (error) {
console.log("ERROR");
console.log(error);
}
else {
console.log("setting token via request")
let token = response.json().token;
jar.set(domain, "session", token, (err, cookie) => {
if (err) console.log(err);
else console.log("COOKIE:", cookie)
})
}
})
console.log("after sending") //**guard three - reached every time**
}
I am using express to return an api response retrieved via a request call.
router.post('/', function(req, res){
var options = {
...
}
rp(options)
.then(function (parsedBody) {
console.log(parsedBody);
res.json(parsedBody)
})
The console log displays the expected payload, also when I use Postman I also see the expected payload.
When though my client app gets the response it is:
Response {type: "cors", url: "http://localhost:3001/api/", redirected: false, status: 200, ok: true, …}
Ive tried adding CORS middleware:
app.use(function(request, response, next) {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers",
"Origin, X-Rquested-With, Content-Type, Accept");
next();
});
My client is a very simple react app using fetch:
fetch('http://localhost:3001/api/', {
method: 'POST',
dataType: 'JSON',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
}).then((response) => {
console.log(response);
}).catch((response) => {
console.log('Error');
});
My react app is localhost:3000 and the express api localhost:3001, the expected payload is a very simple object...
{
athlete: {
firstname: "Matt"
...
}
}
How can I just forward on the api request response to the clients fetch success method?
The problem is not CORS related, the response within the react app needed parsing as json:
.then((response) => {
console.log(response.json());
})
I was trying to make an API call to another domain, which has no-cors enabled.
The API call was made something like this:
let url = `https:sampleApiUrl?params=xxxx`;
console.log("hitting dashboard url")
get(url, token)
.then((resp) => {
console.log("resp", resp)
})
.catch((error) => {
console.log(error)
})
This API call, subsequently calls a 'get' method:
const get = (url, authToken) => {
return baseFetch(url, 'get', false, authToken).then(response => {
if (response.status >= 200 && response.status < 300) {
return response.json();
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
});
}
Now, this get method calls a baseFetch method:
const baseFetch = (url, verb, body, authToken) => {
const request = {
method: verb,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Allow-Origin': '*',
'credentials': 'include'
},
mode: 'cors'
}
if (authToken){
// adding x-access-token in the request header
request.headers['x-access-token'] = authToken;
}
if (body){
request.body = JSON.stringify(body);
}
return fetch(url, request);
}
Now, when this API call is requested, I can't see the "x-access-token" populated in the browser network call.
No x-access-token in request-headers
Also, I am not sure why I get status code 204 in response.
Calling this API from postman and directly from browser or calling as a curl request, returns the correct response.
Thanks
Looking at the image, you are looking at the headers for pre flight OPTIONS method and not the GET method. The pre flght request is generated by the browser and it never has any custom header. therefore it did not have the x-access-token in its headers.