How can I persist auth state in a nodejs app - javascript

So, I am learning NodeJs by creating this backend that fetches some data from a third-party API, the API requires auth. I couldn't figure out how to avoid sending an auth request to the third-party API whenever I wanted to fetch data from it. is there any way I could store the auth state in the app?
const axios = require("axios");
const AUTH_URL = process.env.AUTH_URL;
const REPORT_BASE_URL = process.env.REPORT_BASE_URL;
const X_API_KEY = process.env.X_API_KEY;
const getCompanies = async (req, res) => {
let idToken;
// auth
const authPayload = JSON.stringify({
// ...
});
const config = {
method: "post",
// ...
};
try {
const { data } = await axios(config);
idToken = data.idToken; // set idToken necessary for fetching companies
} catch (error) {
console.log(error);
}
// get company by full text query
const { full_text_query } = req.query;
if (!full_text_query)
return res.send("No full_text_query parameter provided");
try {
const { data } = await axios.get(
`${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`,
{
headers: {
"x-api-key": X_API_KEY,
Accept: "application/json",
authorization: idToken,
},
}
);
res.status(200).json(data);
} catch (error) {
console.log(error);
}
};
module.exports = {
getCompanies,
};

You can break out a function like fetchIdToken and store a Promise that resolves with the idToken in memory.
let idTokenPromise;
async function fetchIdToken () {
if (idTokenPromise) return idTokenPromise;
return idTokenPromise = new Promise(async (resolve) => {
...
resolve(data.idToken);
})
}
You can then use await fetchIdToken() at the start of getCompanies.
You can also just store the idToken in memory. This is slightly simpler, but does mean that you can have a race-condition when multiple getCompanies requests happen at the same time:
let idToken;
async function fetchIdToken () {
if (idToken) return idToken;
...
idToken = data.idToken;
return idToken;
}

Related

how to pass parameter from client side to node-get request

i have a problem.
i have an object in my client side witch have to be my params.
anyone know how to pass an object from client side to server side?
this is my node route, i need to use endPoint from user to do query params
const fetch = require("node-fetch");
async function getDataByEndPoint(req, res, endPoint) {
const url = "https://pixabay.com/api/?key=<KEY-GOES-HERE>";
const options = {
method: "GET",
// headers: {
// "X-RapidAPI-Host": "famous-quotes4.p.rapidapi.com",
// "X-RapidAPI-Key": "your-rapidapi-key",
// },
};
try {
let response = await fetch(url, options);
response = await response.json();
res.status(200).json(response);
console.log(res);
} catch (err) {
console.log(err);
res.status(500).json({ msg: `Internal Server Error.` });
}
}
module.exports = getDataByEndPoint;
this is my client side code
function App() {
const data = useSelector((state) => state.counter.data);
const endPoint = useSelector((state) => state.counter.endPoint);
const dispatch = useDispatch();
// const [data, setData] = useState([]);
const getData = async (endPoint) => {
const requestOptions = {
method: "GET",
};
try {
const res = await fetch(
`http://localhost:8000/dataByParams`,
requestOptions
);
const resJson = await res.json();
console.log(resJson);
dispatch(setData(resJson.hits));
} catch (err) {
console.log(err);
}
};
unfortunately i cant do
?params=
be because i used node fetch so its doesn't work on my localhost....
i tried to use params on https://pixabay.com/api/?key=<KEY-GOES-HERE>
and its worked but when i use it on local host it doesn't because its not the full url, its just the local host

Django + React Axios instance header conflict?

I have all my functions based views on django protected with #permission_classes([IsAuthenticated]) so I have to send a JWT as Bearer token on every request.
In the first version I was using this code:
import axios from 'axios';
import { decodeUserJWT } from '../../extras'
const user = JSON.parse(localStorage.getItem("user"));
var decoded = decodeUserJWT(user.access);
var user_id = decoded.user_id
const instance = axios.create({
baseURL: 'http://localhost:8000/api',
headers: {Authorization: 'Bearer ' + user.access},
params: {userAuth: user_id}
});
export default instance;
Everything was working fine.
But then I added interceptors so I could handle the refreshToken process:
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// const uid = await decodeUserJWT(token);
config.headers["Authorization"] = 'Bearer ' + token;
// config.headers["userAuth"] = uid;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const { dispatch } = store;
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/token/obtain/" && err.response) {
console.log("TOKEN INTERCEPTOR");
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/token/refresh/", {
refresh: TokenService.getLocalRefreshToken(),
});
const { access } = rs.data;
dispatch(refreshToken(access));
TokenService.updateLocalAccessToken(access);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
What happens?
When I add the line config.headers["userAuth"] = uid; the django server console starts showing up that when the react app tries to access the routes it gets a Not Authorized, and when I take that line off de code ... it works fine.
I also tried to pass the param userAuth in the axios.create and keep only the Bearer config inside the interpector code, but still no positive result, the code with the interpector code only works when I take off the userAuth line from axios.
Any ideia on why this is happening and how can I fix this?

error retrieving user Id token assigned by firebase in the client side

I am using JWT based authentication using firebase Admin SDK in express js.
according to the sign in with custom token when we sign the user with the function signInWithCustomToken(token) firebase sets a user-id token for that user.
according to retrieve id tokens
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
we can get the token if the user is logged in
but executing this I get error that getIdToken value is null.
i changed the code to
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
console.log(error)
});
const userToken = await token;
const getData = async (userToken) => {
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: userToke})
})
const data = await response.json()
console.log(responnse)
}
}
getUser();
but still receiving the same error
I looked up for some solutions and found similar answers to the question one of which I implemented was solution
it used onAuthStateChanged method and I am using
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
in cdn but now am getting
Uncaught (in promise) TypeError: firebase.auth.onAuthStateChanged is not a function
at profile:40
at new Promise (<anonymous>)
at getIdTokenRefreshed (profile:37)
at profile:50
I changed the above code to this
firebase.initializeApp(firebaseConfig);
const getIdTokenRefreshed = async () => {
return new Promise(async (resolve, reject) => {
const unsubscribe = await firebase
.auth
.onAuthStateChanged(async user => {
unsubscribe()
const refreshedToken = await user
.getIdToken(true)
.catch(err => console.error(err))
resolve(refreshedToken)
console.log(refreshedToken)
}, reject)
});
}
getIdTokenRefreshed();
still getting the second error where onAuthStateChanged is not defined
how do I retrieve the user id token?
UPDATE
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
after implementing the above code this is the error
await is only valid in async functions and the top level bodies of modules
First, I'd recommend updating Firebase SDK to latest version which is 8.9.1 at the time of writing this.
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-auth.js"></script>
If you take a look at onAuthStateChanged part in the documentation, it should be:
firebase.auth().onAuthStateChanged(...)
// ^^
// not firebase.auth.onAuthStateChanged
The onAuthStateChanged won't be triggered unless you call the getIdTokenRefreshed function. You can simply refactor that function to:
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
Lastly, the variable name is userToken but in request body it is body: JSON.stringify({idToken: userToke}) and you don't need an await before a variable name. Try refactoring the getUser function to:
//const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
// console.log(error)
//});
//const userToken = await token;
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(true)
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: token})
})
const data = await response.json()
console.log(data)
return data
}
getUser().then(data => {
console.log("Data received")
})

Handling query in React and Express

Somewhere in my React application I used REST API to send request to the server. In my URL I want to use query (in the postIconsTransition method), but when I send a request to the server, server tells me could not found this URL (I build this error in my server). If I use this URL without any query the request in the postIconsTransition method works fine. postId and authContext.userId work fine, can anyone tell me what's wrong with my code?
In my component where I send request:
const likeHandler = async () => {
setLike(prevState => !prevState);
if (!like) {
try {
await postIconsTransition(props.postId, "inc");
} catch (error) {}
} else {
try {
await postIconsTransition(props.postId, "dec");
} catch (error) {}
}
};
In useHttp.js component:
const postIconsTransition = async (postId, addtionAddress) => {
return await transitionData(
`http://localhost:5000/post/${postId}/${authContext.userId}?t=${addtionAddress}`,
"POST",
null,
{ Authorization: `Bearer ${authContext.token}` }
);
};
transitionData method:
const transitionData = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const abortController = new AbortController();
activeHttpRequest.current.push(abortController);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: abortController.signal
});
const responseData = await response.json();
activeHttpRequest.current = activeHttpRequest.current.filter(
reqCtrl => reqCtrl !== abortController
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (error) {
modalContext.err(error);
setIsLoading(false);
throw error;
}
},
[modalContext.err]
);
In Express:
router.post(
"/:postId/:userId?t=inc",
tokenChecker,
postController.updateLikesComments
);
router.post(
"/:postId/:userId?t=dec",
tokenChecker,
postController.updateLikesComments
);
All of them work fine but when I use query in my URL, it's not working any more.
You don't specify query parameters in express routes like that. Just send them. Express can read it.
router.post(
"/:postId/:userId",
tokenChecker,
postController.updateLikesComments
);
// Notice that you don't need the other one.
and in your controller check the parameter
// controller's code
const t = req.query.t;
if (t === 'inc') {
// do what you want here
}
if (t === 'dec') {
// do what you want here
}

Make cache persist even if Node process dies

My backend, Nodejs, has to make some API GET request calls to external services to retrieve data and serve as a webpage. These API calls are being cached.
Since I'm using Heroku, this data is being removed every time the apps enter to hibernate state. Is there any cache library that persists? Currently I'm using lru-cache and axios-extensions.
const axios = require('axios');
const { cacheAdapterEnhancer } = require('axios-extensions');
const LRUCache = require("lru-cache")
const options = {
defaultCache: new LRUCache({ maxAge: 60 * 60 * 1000, max: 100 })
}
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000,
adapter: cacheAdapterEnhancer(axios.defaults.adapter)
}
)
getData: async () => {
try {
const response = await http.get(url, config)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}
As suggested by #giankotarola in comments, I needed to use cache DB to solve the problem. Finally I managed to make it work using Redis! My final code:
const axios = require('axios')
const redis = require('redis')
// create and connect redis client to local instance.
const client = redis.createClient()
// Print redis errors to the console
client.on('error', (err) => {
console.log("Error " + err)
});
const http = axios.create({
headers: { 'Cache-Control': 'no-cache' },
timeout: 60000
}
)
let config = {
headers: {
Authorization: `Bearer ${API_KEY}`,
}
}
let url = 'https://example.com'
module.exports={
getData: async () => {
try {
// Try to get response from Redis store
const response = await client.get(url)
return responseJSON = JSON.parse(response)
} catch (err) {
try {
const response = await http.get(url, config)
const data = response.data
// Save response in Redis store
client.setex(url, 60 * 60 * 1000, JSON.stringify(data));
return data
} catch (error) {
console.log(error)
}
}
}
}

Categories

Resources