I am having a difficult time understanding why my API call does not work in axios (relatively new to JS). I have built an API server that takes in an Authorization header with a JWT token.
Here is my POST request workflow in Python:
resp = requests.post('http://127.0.0.1:8000/api/v1/login/access-token', data={'username': 'admin#xyz.com', 'password': 'password'})
token = resp.json()['access_token']
test = requests.post('http://127.0.0.1:8000/api/v1/login/test-token', headers={'Authorization': f'Bearer {token}'})
# ALL SUCCESSFUL
Using axios:
const handleLogin = () => {
const params = new URLSearchParams();
params.append('username', username.value);
params.append('password', password.value);
setError(null);
setLoading(true);
axios.post('http://localhost:8000/api/v1/login/access-token', params).then(response => {
console.log(response)
setLoading(false);
setUserSession(response.data.access_token);
props.history.push('/dashboard');
}).catch(error => {
setLoading(false);
console.log(error.response)
if (error.response.status === 401) {
setError(error.response.data.message);
} else {
setError("Something went wrong. Please try again later.");
}
});
}
// the above works fine
// however:
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => {
const token = getToken();
if (!token) {
return;
}
axios.post(`http://localhost:8000/api/v1/login/test-token`, {
headers: {
'Authorization': 'Bearer ' + token
}
}).then(response => {
// setUserSession(response.data.token);
console.log('we made it')
setAuthLoading(false);
}).catch(error => {
removeUserSession();
setAuthLoading(false);
});
}, []);
if (authLoading && getToken()) {
return <div className="content">Checking Authentication...</div>
}
// RETURNS A 401 Unauthorized response...
What is different about the two above requests? Why does the axios version return different results than requests?
In my API, CORS have been set to *, and I know that the token within Axios is being saved properly in sessionStorage.
Any ideas?
As far as I can see you are passing your username and password in axios as params and as body data in your python request, I am not sure if your backend expects it as params or body data but try changing const params = new URLSearchParams(); to
const params = new FormData(); if the problem is that the backend isn't getting the body data it needs. The best thing I could recommend is checking your browser network tab and seeing what exactly the problem is when you hit your server.
Related
I have two URLs. The first one is for login post request and the second URL is to call for a json. I can not get the json file from URL, if I am not logged in. How can I create a session so I can do more than one post request or alternatively how can i use JWT or set-cookie for authentication?
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
const login_url = "https://www.example.com/login"
const json_url = "https://www.example.com/users/anastasia"
const data = {"username": "my_username", "password": "my_password"}
const otherParm = {
headers:{"content-type": 'application/json'},
body:JSON.stringify(data),
method:"POST"
};
fetch(login_url, otherParm)
//.then(data=>{return data.json()})
.then(res=>{
console.log(res.headers.get('jwt'))
})
.catch(err=>console.log(err))
The response is:
distinct_id: 156841653654e68w,
jwt: e1616er5hw16e35hw6e5h6eth
fetch(json_url).then(function (response) {
return response.json();
}).then(function (obt) {
console.log(obt);
}).catch(function(error) {
console.error('something went wrong with retrieving the json file');
console.error(error);
});
The response is: 401 Unauthorized
I'm trying to implement the Facebook OAuth in my express/NodeJS app using authorization code flow. I'm using react-facebook-login node module to fetch the authorization code. In my react app, I could get the authorization code successfully. But in server side, I can't request the access token from the Facebook API as I'm getting an error message "redirect_uri is not identical to the one you used in the OAuth dialog request"
Code in my react app,
facebookLogin = async (signedRequest) => {
return fetch('/api/auth/facebook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ signedRequest }),
}).then((res) => {
if (res.ok) {
return res.json();
} else {
return Promise.reject(res);
}
});
};
responseFacebook = async (response) => {
try {
if (response['signedRequest']) {
const userProfile = await this.facebookLogin(response['signedRequest']);
console.log(userProfile);
} else {
throw new Error(response.error);
}
} catch (err) {
console.log(err);
}
};
render() {
<FacebookLogin
appId={process.env.FACEBOOK_CLIENT_ID}
fields="name,email"
responseType="code"
redirectUri="http://localhost:3000/"
callback={this.responseFacebook}
/>
In my app.js
const facebookOAuth = require('./config/facebookOAuth');
// facebook oauth route
app.post("/api/auth/facebook", async (req, res) => {
try {
const signedRequest = req.body.signedRequest;
const profile = await facebookOAuth.getProfile(signedRequest);
console.log(profile);
res.send({ profile });
} catch (err) {
console.log(err);
res.status(401).send();
}
});
facebookOAuth.js look like this
const fetch = require('node-fetch');
const getData = async (userId, accessToken) => {
const userData = await fetch(`https://graph.facebook.com/${userId}?fields=name,email&access_token=${accessToken}`, {
method: 'GET'
}).then((res) => {
return res.json();
}).then((userData) => {
return userData;
});
return userData;
};
exports.getProfile = async (signedRequest) => {
const decodedSignedRequest = JSON.parse(Buffer.from((signedRequest.split(".")[1]), 'base64').toString());
const profile = await fetch(`https://graph.facebook.com/oauth/access_token?client_id=${process.env.FACEBOOK_CLIENT_ID}&redirect_uri=${encodeURIComponent('http://localhost:3000/')}&client_secret=${process.env.FACEBOOK_CLIENT_SECRET}&code=${decodedSignedRequest.code}`, {
method: 'GET'
}).then((res) => {
return res.json();
}).then((token) => {
console.log(token);
const userData = getData(decodedSignedRequest.user_id, token.access_token);
return userData;
}).catch((err) => {
console.log(err);
return err;
});
return profile;
}
What I'm getting is this error
"error": {
message: 'Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request',
type: 'OAuthException',
code: 100,
error_subcode: 36008,
fbtrace_id: 'A-YAgSqKbzPR94XL8QjIyHn'
}
I think the problem lies in my redirect_uri. Apparently, the redirect uri I obtained from the Facebook auth dialog is different from the one that I'm passing to the facebook API in my server side (http://localhost:3000/).
I believe there's something to do with the origin parameter of the redirect_uri. Initial auth dialog request uri indicates that it's origin parameter value is something like "origin=localhost:3000/f370b6cb4b5a9c". I don't know why react-facebook-login add some sort of trailing value at the end of origin param.
https://web.facebook.com/v2.3/dialog/oauth?app_id=249141440286033&auth_type=&cbt=1620173773354&channel_url=https://staticxx.facebook.com/x/connect/xd_arbiter/?version=46#cb=f39300d6265e5c4&domain=localhost&origin=http%3A%2F%2Flocalhost%3A3000%2Ff370b6cb4b5a9c&relation=opener&client_id=249141440286033&display=popup&domain=localhost&e2e={}&fallback_redirect_uri=http://localhost:3000/&locale=en_US&logger_id=f1b3fba38c5e31c&origin=1&redirect_uri=https://staticxx.facebook.com/x/connect/xd_arbiter/?version=46#cb=f17641be4cce4d4&domain=localhost&origin=http%3A%2F%2Flocalhost%3A3000%2Ff370b6cb4b5a9c&relation=opener&frame=f3960892790a6d4&response_type=token,signed_request,graph_domain&return_scopes=false&scope=public_profile,email&sdk=joey&version=v2.3
I tried finding everywhere about this but no luck. Anyone has clue about this, much appreciated.
Are you using middleware to parse the body? if you aren't code could be undefined here.
const facebookOAuth = require('./config/facebookOAuth');
// facebook oauth route
app.post("/api/auth/facebook", async (req, res) => {
try {
const code = req.body.code;
const profile = await facebookOAuth.getProfile(code);
console.log(profile);
res.send({ profile });
} catch (err) {
console.log(err);
res.status(401).send();
}
});
I'm getting the error when I'm requesting my Laravel backend with Axios and getting the data back and requesting another API for some data and that goes well but after the API returns the data and I request my laravel backend to store the data. It returns a response with
app.js:360 POST http://technotrace.tech/session-adding-api 419 (unknown status)
I've tried checking on my PHP backend and this only stops if I'm disabling the VerifyCSRFToken middleware group.
But I need to check the CSRF token anyway for security reasons.
I'm able to understand why the csrf token gets changed on sending request.
If I'm doing just sending the request to my laravel backend then everything goes right but when I'm requesting another api then the problem arrives on sending the request to my backend.
function addSession(url, formdata) {
//Add Session
axios.post(url, formdata)
.then(res => {
console.log(res)
var data = res.data;
if ((data.session_info != null && data.session_info != {})) {
console.log(data.session_info);
}
})
.catch(err => {
console.error(err);
})
}
function getApiDetails() {
//Start getting api details
axios.get('https://ipapi.co/json/').then(res => {
console.log(res)
var csrf = jr('meta[name="csrf-token"]').get(0).content;
var ses_id = jr('meta[name="verify-token"]').get(0).content;
var cur_url = jr('link[rel="canonical"]').get(0).href;
// console.log(csrf, ses_id, cur_url);
var url = '/session-adding-api';
var formdata = new FormData();
formdata.append('_token', csrf);
formdata.append('session_token', ses_id);
formdata.append('session_repeat', 0);
formdata.append('current_url', cur_url);
Object.entries(res.data).forEach(entry => {
// console.log(entry[0], entry[1]);
formdata.append(entry[0], entry[1]);
});
addSession(url, formdata);
})
.catch(err => {
console.error(err);
})
//End getting api details
}
function matchSession() {
//Match Session Start
var csrf = jr('meta[name="csrf-token"]').get(0).content;
var ses_id = jr('input[name="verify-token"]').get(0).content;
var url = '/session-matching-api';
var formdata = new FormData();
formdata.append('_token', csrf);
formdata.append('session_token', ses_id);
axios.post(url, formdata)
.then(res => {
console.log(res)
var data = res.data;
if ((data.match != true)) {
getApiDetails()
}
})
.catch(err => {
console.error(err);
})
//Match Session
}
matchSession();
In my VUE components, I use this async method to fetch data from API:
Components:
methods: {
async fetch() {
// console.log("##### WAIT ####");
const { data } = await staffRepository.getItems(this.teamId)
// console.log("##### END WAIT ####");
this.staffs = data
},
},
As you can see I use a custom repository to have a single axios code, this repository is imported in my previous component.
staffRepository:
export default {
getItems(nationId) {
return Repository.get(`page/${nationId}`)
},
}
And finally the main repository having the axios code:
Repository:
import axios from 'axios/index'
const baseDomain = 'https://my end point'
const baseURL = `${baseDomain}`
...
const headers = {
'X-CSRF-TOKEN': token,
// 'Access-Control-Allow-Origin': '*', // IF you ADD it add 'allowedHeaders' to ai server config/cors.php
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtoken}`,
}
export default axios.create({
baseURL,
withCredentials: withCredentials,
headers: headers,
})
This code works very nice when the jwtoken is a valid and NOT EXIPRED token.
The problem is when the token is expired or not found and my laravel 5.8 API returns the status code 401 (or other).
GET https://api.endpoint 401 (Unauthorized)
A good solution could catch the status code in staffRepository, the one having the get method.
MySolution: (not working)
getItems(nationId) {
return Repository.get(`page/${nationId}`)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response.status) // <-- it works!
})
},
This could be nice because in error case the error in console is 401
But I can't use this solution because I have 2 nested promises: this one and the async fetch() into the component.
How can I fix it still using my repository environment?
I would suggest using the returned promise in your component, to make things more explicit:
methods: {
fetch() {
let data = null
staffRepository
.getItems(this.teamId)
.then(data => {
// do something with data
this.staffs = data
})
.catch(e => {
// do something with error, or tell the user
})
},
},
Edit - this will work perfectly fine, as your method in Repository will return a promise by default if you are using axios.
Try this: API code, where HTTP is an axios instance
export const get = (path: string): Promise<any> => {
return new Promise((resolve, reject) => {
HTTP.get(`${path}`)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(handleError(error));
});
});
};
// ***** Handle errors *****/
export function handleError(error) {
if (error.response) {
const status = error.response.status;
switch (status) {
case 400:
// do something
break;
case 401:
// do something, maybe log user out
break;
case 403:
break;
case 500:
// server error...
break;
default:
// handle normal errors here
}
}
return error; // Return the error message, or whatever you want to your components/vue files
}
The best practice solution is to use axios's interceptors:
import axios from "axios";
import Cookies from "js-cookie";
export default (options = {}) => {
let client = options.client || axios.create({ baseURL: process.env.baseUrl });
let token = options.token || Cookies.get("token");
let refreshToken = options.refreshToken || Cookies.get("refreshToken");
let refreshRequest = null;
client.interceptors.request.use(
config => {
if (!token) {
return config;
}
const newConfig = {
headers: {},
...config
};
newConfig.headers.Authorization = `Bearer ${token}`;
return newConfig;
},
e => Promise.reject(e)
);
client.interceptors.response.use(
r => r,
async error => {
if (
!refreshToken ||
error.response.status !== 401 ||
error.config.retry
) {
throw error;
}
if (!refreshRequest) {
refreshRequest = client.post("/auth/refresh", {
refreshToken
});
}
const { data } = await refreshRequest;
const { token: _token, refreshToken: _refreshToken } = data.content;
token = _token;
Cookies.set("token", token);
refreshRequest = _refreshToken;
Cookies.set("refreshToken", _refreshToken);
const newRequest = {
...error.config,
retry: true
};
return client(newRequest);
}
);
return client;
};
Take a look at client.interceptors.response.use. Also you should have a refreshToken. We are intercepting 401 response and sending post request to refresh our token, then waiting for a new fresh token and resending our previous request. It's very elegant and tested solution that fits my company needs, and probably will fit your needs too.
To send request use:
import api from './api'
async function me() {
try {
const res = await api().get('/auth/me')
// api().post('/auth/login', body) <--- POST
if (res.status === 200) { alert('success') }
} catch(e) {
// do whatever you want with the error
}
}
Refresh token: The refresh token is used to generate a new access
token. Typically, if the access token has an expiration date, once it
expires, the user would have to authenticate again to obtain an access
token. With refresh token, this step can be skipped and with a request
to the API get a new access token that allows the user to continue
accessing the application resources.
I am trying to debug an issue I am having with interceptors in a access token and refresh token reqwuirement. I tried to follow debugging procedures from: Axios interceptors and asynchronous login and also follow the axios interceptor format and procedure from: https://shinework.io/post/handle-oauth2-authentication-with-react-and-flux
However, I can't seem to figure out why my application is not working. I'm new to interceptors and I think it may be an issue with how they are being ejected? I run into an issue during my axios request where I try to run the initial request with an updated access token received from a refresh endpoint. I have this code within my root index.js file
When I debug after the access token has expired, the console.log of 'Rejecting' during the catch block of the initial request call utilizing the new access token renders. Again the purpose is to utilize the refresh token to grab a new sets of access_token and refresh_token, then make the initial request call utilizing the new access_token.
Request and Response Interceptors:
axios.interceptors.request.use(
config => {
const token = localStorage.getItem('access_token');
config.headers.authorization = `Bearer ${token}`;
return config;
},
error => {
return Promise.reject(error);
},
);
axios.interceptors.response.use(
response => {
return response;
},
error => {
const errorMessage = error.message;
const substring = '401';
const errorCheck = errorMessage.includes(substring);
return new Promise((resolve, reject) => {
if (errorCheck) {
onRefreshToken({
initialRequest: error.config,
resolve,
reject,
});
} else {
refreshFailLogout(store);
reject(error);
}
});
},
);
onRefreshToken() and saveTokens()
const onRefreshToken = params => {
let refreshToken = store.getState().auth.refresh_token;
if (refreshToken) {
axios.interceptors.request.eject(InterceptorUtil.getInterceptor());
const dataSet = {
refresh_token: `${refreshToken}`,
};
axios
.post('https://localhost:3469/api/Login/$refresh', dataSet)
.then(response => {
saveTokens(response.data);
// Replay request
axios(params.initialRequest)
.then(response => {
params.resolve(response);
store.dispatch({ type: AUTHENTICATED, payload: response.data });
})
.catch(response => {
console.log('Rejecting')
params.reject(response);
})
.catch(() => {
refreshFailLogout();
});
});
}
};
const saveTokens = response => {
const {
access_token,
refresh_token,
scope,
id_token,
token_type,
} = response;
// ...local storage save of variables
let token = localStorage.getItem('access_token');
let interceptor = axios.interceptors.request.use(config => {
config.headers.authorization = `Bearer ${token}`;
return config;
});
InterceptorUtil.setInterceptor(interceptor);
};
InterceptorUtil Class
class InterceptorUtil {
constuctor() {
this.interceptor = null;
}
setInterceptor(interceptor) {
this.interceptor = interceptor;
}
getInterceptor() {
return this.interceptor;
}
}
export default new InterceptorUtil();