I'm currently building an app using React Native on the frontend and Node.js together with express on the backend.
I have two axios instances, one of which I call the main server and the other the authentication server. With both instances all normal server calls outside of the following file are working expect this one. I'm currently localhosting both servers.
My frontend looks like this:
import axios from 'axios';
import AsyncStorage from '#react-native-async-storage/async-storage';
const BASE_URL = 'URL_PLACEHOLDER'; // <- IP Address of my computer
export const instanceMain = axios.create({
baseURL: `${BASE_URL}:3000`,
timeout: 1000,
});
export const instanceAuth = axios.create({
baseURL: `${BASE_URL}:4000`,
});
// Interception to check if a token refresh is needed
instanceMain.interceptors.request.use(async function (response) {
const accessToken = await AsyncStorage.getItem('accessToken');
const refreshToken = await AsyncStorage.getItem('refreshToken');
await instanceAuth
.post(`/token`, {
accessToken: accessToken,
refreshToken: refreshToken,
})
.then((res) => {
AsyncStorage.setItem('accessToken', res.data.accessToken);
AsyncStorage.setItem('refreshToken', res.data.refreshToken);
})
.catch((error) => {
console.log(error);
});
return response;
});
My backend of the post axios is trying to make looks like this:
app.post('/token', async (req, res) => {
try {
const accessToken = req.body.accessToken;
const refreshToken = req.body.refreshToken;
if (accessToken == null || accessToken == undefined)
return res.sendStatus(401);
// Verifying AccessToken
jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET, (error) => {
// If it is expired this should be executed. I think I'm probably handling this
// the wrong way, but this was the only way I could think of.
if (error) {
// tokenDB is an in-storage json database containing all active refresh tokens
tokenDB.read();
tokenDB.data ||= { tokens: [] };
if (refreshToken == null) return res.sendStatus(401);
// Checking if tokenDB does not contain the refresh token, if so, return 403
if (!tokenDB.data.tokens.includes(refreshToken)) {
return res.sendStatus(403);
}
// Verifying RefreshToken
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
(error, account) => {
// Deleting iat from account, otherwise the new tokens would be the same
// as before
delete account.iat;
if (error) return res.sendStatus(403);
// Generating new Tokens
const newAccessToken = generateAccessToken(account);
const newRefreshToken = generateRefreshToken(account);
// Removing old refreshToken from tokenDB and push newRefreshToken to it
tokenDB.data.tokens = tokenDB.data.tokens.filter(
(token) => token !== refreshToken
);
tokenDB.data.tokens.push(newRefreshToken);
// Result: new Access and Refresh token
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken,
});
tokenDB.write();
}
);
} else {
return res.sendStatus(100);
}
});
} catch (error) {
console.error(error);
}
});
The error message simply says [AxiosError: Network Error]. error.config gives back the following, but I can't do much with it.
{"adapter": [Function xhrAdapter], "baseURL": "IP_PLACEHOLDER:4000", "data": "{\"accessToken\":\"ACCESS_TOKEN_PLACEHOLDER",\"refreshToken\":\"REFRESH_TOKEN_PLACEHOLDER"}", "env": {"Blob": [Function Blob], "FormData": [Function FormData]}, "headers": {"Accept": "application/json, text/plain, */*", "Content-Type": "application/json"}, "maxBodyLength": -1, "maxContentLength": -1, "method": "post", "timeout": 0, "transformRequest": [[Function transformRequest]], "transformResponse": [[Function transformResponse]], "transitional": {"clarifyTimeoutError": false, "forcedJSONParsing": true, "silentJSONParsing": true}, "url": "/token", "validateStatus": [Function validateStatus], "xsrfCookieName": "XSRF-TOKEN", "xsrfHeaderName": "X-XSRF-TOKEN"}
I hope I can find some help here, Thanks.
Use redux and proper async/await function to use. Otherwise, Axios return an error.
enter link description here
recommendation
use better only node.js and express ,axios isn't necesary because express is same
Related
I have a quick question. I am using Axios to send requests to the nodejs API, when I set the token in the request header the API returns "jwt must be provided". The API expects the token with a custom name attached to it - here's how far I've gotten.
Snippet of API code that sends the token on login:
const token = jwt.sign(
{
userID: result[0].userID,
firstName: result[0].firstName,
lastName: result[0].lastName,
email: result[0].email,
role: result[0].role,
// exp: Math.floor(Date.now() / 1000) + 60 * 60,
},
"HeyImaPrivateKeyyy"
);
res.json({ token });
console.log("Login Attempt", res.statusMessage, req.body);
} else {
res.status(400).send({ message: "Invalid credentials!" });
console.log("Login Attempt", res.statusMessage, req.body);
}
-- React code from here --
Response from API on successful login:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOjEsImZpcnN0TmFtZSI6IkNhbWVyb24iLCJsYXN0TmFtZSI6IkVyYXNtdXMiLCJlbWFpbCI6ImNhbWVyb25AY2xpZnRjb2xsZWdlLmNvbSIsInJvbGUiOiJzdXBlckFkbWluIiwiaWF0IjoxNjYzMzEzNTM2fQ.9R6vXn-5Vb5fj48eUJGPNUGnXMw9TXOjJCox7U36WMI"
}
Saving the token on successful login (React)
const login = async ({ email, password }) => {
const res = await api.post(
"/auth",
{
email: email, //varEmail is a variable which holds the email
password: password,
},
{
headers: {
"Content-type": "application/json",
Authorization: false,
},
}
);
const { from } = state || {};
let token = jwt(res.data.token);
setToken("x-auth-token", token); // your token
localStorage.setItem("x-auth-token", res.data.token);
localStorage.setItem("userLogged", true);
localStorage.setItem("name", token.firstName);
localStorage.setItem("role", token.role);
navigate("/auth/dashboard" || from.pathname, { replace: true });
};
Here is the React component that is trying to call the API:
const [count, setCount] = useState(null);
const token = localStorage.getItem("x-auth-token");
const studentCount = useEffect(() => {
const config = {
headers: { "x-auth-token": token },
"Content-type": "application/json",
};
api.get("/students/", {}, config).then((response) => {
setCount(response.data);
});
}, [token]);
if (!count) return null;
This is what the API is expecting on request:
export const teacher = (req, res, next) => {
const token = req.header("x-auth-token");
if (!auth && !token)
return res.status(401).send({ message: "Access denied." });
const decoded = jwt.verify(token, "DemoPrivateKey");
if (auth && ["superAdmin", "admin", "teacher"].includes(decoded.role)) {
next();
} else {
res.status(400).send({ message: "Access denied!" });
}
};
Ideally, I would like to send the token as a header on successful login, but it saves as undefined on the client (have no idea how to fix that).
If you're using Axios then, as per the doc, get method should have config parameter in second position not third one.
So maybe, simply updating api.get("/students/", {}, config) into api.get("/students/", config) should solve your issue.
I'm setting up an authentication flow with JWT with my strapi backend and a next.js frontend.
Testing the backend with postman works as expected. I get back a user object and a JWT token.
But calling my backend from my frontend results in a statuscode 500 - internal server error.
I honestly don't see where I'm wrong and I tried to rewrite those calls from scratch again and again. Does anyone see where I have been wrong?
Here's my register.js where the call to my backend happens:
import cookie from 'cookie'
import { API_URL } from '#/config/index'
export default async (req, res) => {
if (req.method === 'POST') {
const { username, email, password } = req.body
const strapiRes = await fetch(`${API_URL}/auth/local/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
email,
password,
}),
})
const data = await strapiRes.json()
if (strapiRes.ok) {
// Set Cookie
res.setHeader(
'Set-Cookie',
cookie.serialize('token', data.jwt, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 60 * 60 * 24 * 7, // 1 week
sameSite: 'strict',
path: '/',
})
)
res.status(200).json({ user: data.user })
} else {
res
.status(data.statusCode)
.json({ message: data.message[0].messages[0].message })
}
} else {
res.setHeader('Allow', ['POST'])
res.status(405).json({ message: `Method ${req.method} not allowed` })
}
This is my register function in my context
const register = async (user) => {
const res = await fetch(`${NEXT_CLIENT_URL}/api/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
})
const data = await res.json()
if (res.ok) {
setUser(data.user)
router.push('/account/dashboard')
} else {
setError(data.message)
setError(null)
}
}
This is how I call the context function in my next frontend
const handleSubmit = (e) => {
e.preventDefault()
if (password !== password2) {
toast.error('Passwords do not match!')
return
}
register({ username, email, password })
}
Glad for every piece of advice! Logging in works as expected in the same app.
I have set up JWT to be set in localstorage whenever someone logins or registers. And it works, I can see the token in localstorage. But when I set the token in the headers with axios, node.js in the backend can`t find the token. Like it does not exists. I have checked it in the front end, I get logs of the token in the headers. And also when I request from postman it works. Here is the code.
setAuthToken function = {
const instance = axios.create({
baseURL: "https://localhost:5000",
});
if (token) {
instance.defaults.headers.common["x-auth-token"] = `${token}`;
console.log(instance.defaults.headers.common["x-auth-token"]);
} else {
delete instance.defaults.headers.common["x-auth-token"];
}
}
const loadUser = async () => {
if (localStorage.token) setAuthToken(localStorage.token);
console.log(localStorage.token);
try {
const res = await axios.get("/api/users");
console.log(res);
dispatch({ type: USER_LOADED, payload: res.data });
} catch (err) {
console.log(err.response.data.msg);
dispatch({ type: AUTH_ERROR });
}
The request comes to the await axios statement and goes to catch so error is in the request.
Here is the backend code
// Get current user
router.get("/", auth, async (req, res) => {
try {
const user = await User.findById(req.user.id);
res.status(200).json({ user });
} catch (err) {
console.log(err);
res.status(500).json({ msg: `Server Error` });
}
});
auth middleware function here = {
const token = req.headers["x-auth-token"];
console.log(token, "token in auth.js");
console.log(req.headers, "req.header");
if (!token) {
return res.status(401).json({ msg: `Access denied.` });
}
try {
const decoded = jwt.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: `Token is not valid` });
}
}
I`m new to backend develoment and axios. Can someone help me please. Thank you
Here are the console.logs
Logs
Logs
Little update, it looks like there is a problem with proxy, I am using my own backend api, and also movie data base api. So maybe thats why I cant set headers? Here are new logs:
config: Object { url: "/api/users", method: "get", timeout: 0, … }
data: "Proxy error: Could not proxy request /api/users from localhost:3000 to http://localhost:5000/ (ECONNREFUSED)."
headers: Object { connection: "keep-alive", date: "Wed, 05 May 2021 13:18:05 GMT", "keep-alive": "timeout=5", … }
request: XMLHttpRequest { readyState: 4, timeout: 0, withCredentials: false, … }
status: 500
statusText: "Internal Server Error
I think the issue is because you are setting you are setting up your instance wrongly
set up your instance in a new file config.js -
import Axios from 'axios';
const baseURL = "http://localhost:5000";
const axiosInstance = Axios.create({
baseURL: baseURL,
});
axiosInstance.interceptors.request.use(
function (config) {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
export default axiosInstance;
now when making any api request instead of using axios use axiosInstance eg-
axiosInstance.get('/something').then(res => console.log(res)).catch(err => console.log(err))
this question has been answered, you can ignore that and click away
i am making my app using react native expo, and after successfully login, my server return an token an i want to store it in the AsyncStorage, this is my code:
import { Alert, AsyncStorage } from "react-native";
export const login_as_student = async (code, password, navigation) => {
return fetch("http://192.168.47.102:5000/users/login/student", {
// Windows + CMD
// Wireless LAN adapter Wi-Fi
method: "POST",
headers: {
"content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify({ code, password }),
})
.then((response) => {
return response.json();
})
.then((json) => {
const { currentUser, token } = json;
console.log(json);
if (token && currentUser) {
AsyncStorage.setItem("token", token)
.then((value) => console.log("token", value))
.catch((error) => console.log(error));
return navigation.navigate("Darshboard");
} else {
console.log(json);
Alert.alert(json.message);
}
})
.catch((err) => {
console.log(err);
Alert.alert(err.message);
});
};
and this is the result:
Object {
"currentUser": Object {
"__v": 0,
"_id": "5f8be6bf0220db23d096ca14",
"activated": false,
"code": "16020503",
"fullname": "Đỗ Xuân An ",
"password": "$2b$10$PQ5EAc3Vkn1WT00uKadYi.ue2UR1lRBfSeF.cXgZps2pqejV4bAUy",
"profileImage": "https://avataaars.io/?accessoriesType=Prescription01&avatarStyle=Circle&clotheColor=Blue02&clotheType=BlazerSwet
er&eyeType=WinkWacky&eyebrowType=FlatNatural&facialHairColor=Platinum&facialHairType=MoustacheFancy&hairColor=Platinum&hatColor=PastelRed&mouthType=Vomit&skinColor=Brown&topType=LongHairMiaWallace",
"role": "student",
"vnumail": "16020503#vnu.edu.vn",
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiXCI1ZjhiZTZiZjAyMjBkYjIzZDA5NmNhMTRcIiIsImlhdCI6MTYwMzU1ODYyOSwiZXhwIjoxNjAzNjQ1MDI5fQ.cxOHxddB_ha94ZGXY_XLcCs1MgDJHhZDgbl4GFOzk80",
}
token null
as you can see, i received a json which contains 2 objects is token and currentUser, after that i stored it in the AsyncStorage but it returns null, i am totally newbie with React Native, thank you for help me out and have a good day :)
update:
thanks everyone, i have been figured it out that the AsyncStorage is no longer imported from "react-native", it is now imported from "#react-native-community/async-storage", have a good day
You are missing await before AsyncStorage.setItem()
Modify your code like this:
.then(async (json) => { // Make this async
const { currentUser, token } = json;
console.log(json);
if (token && currentUser) {
await AsyncStorage.setItem("token", token) // add await here
.then((value) => console.log("token", value))
.catch((error) => console.log(error));
return navigation.navigate("Darshboard");
} else {
console.log(json);
Alert.alert(json.message);
}
})
Are you sure you are getting right (non NULL) token and currentUser values after destructuring?
If you are getting null, wrap the statement within parenthesis
({currentUser, token} = json)
Also, an advice, better mask or hide the token value or any other sensitive info before posting :)
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.