in my React Native app I receive a token from an API. Everytime the app sends a request to the server this token is needed. I save the token in the AsyncStorage:
export const onSignIn = (value) => AsyncStorage.setItem('USER_TOKEN', value);
In many different parts of the app I need this token and therefore I wanted to use a function, that extracts the information out of the token:
export const getTokenInfo = async () => {
try{
const value = await AsyncStorage.getItem('USER_TOKEN')
.then((res) => {
const jsonData = jwtDecode(res);
return jsonData;
})
}
catch(e){
console.log('caught error', e);
}
}
When calling the function in other Components it just returns the Promise itself and not the token. Is there a possibility to get the token, but not the promise? A possible approach was to use setState() to store the token in a state, but there are some components like DrawerNavigator that are not in a class.
Thanks!
Your forgot to return the value on your getTokeninfo function
export const getTokenInfo = async () => {
try{
const value = await AsyncStorage.getItem('USER_TOKEN')
.then((res) => {
const jsonData = jwtDecode(res);
return jsonData;
})
return value // <---- you forgot this line
}
catch(e){
console.log('caught error', e);
}
}
Related
I'm buiding React Native Expo App with external rest api.
I have created reusable axios api call:
// axiosAPi.js
export const axiosApi = async (method, url, obj = {}) => {
try {
switch (method) {
case 'GET':
return await axios
.get(`${baseUrl}/${url}`, config)
.then((res) => res.data)
case 'POST':
return await axios
.post(`${baseUrl}/${url}`, obj, config)
.then((res) => res.data)
case 'PUT':
return await axios
.put(`${baseUrl}/${url}`, obj, config)
.then((res) => res.data)
case 'DELETE':
return await axios
.delete(`${baseUrl}/${url}`, config)
.then((res) => res.data)
}
} catch (error) {
throw error?.response?.data?.error
}
}
I have created a hook with login instance using react-query:
// api/index.js
export default function useApiHook() {
const login = useMutation((obj) => axiosApi('POST', `auth/login`, obj))
return { login }
}
Here is the implementation of login screen
// screens/login.js
const loginPostMutation = useApiHook()?.login
const submitHandler = (data) => {
loginPostMutation
?.mutateAsync(data)
?.then((res) => res)
.catch((err) => err)
}
When I send correct credentials is returns the data with no errors, but when I send incorrect credentials it returns the error + this warning in the console:
Invalid credentials
at node_modules/#tanstack/query-core/build/lib/mutation.js:153:10 in Mutation#execute
The line in question points towards react-query logging the error to the console in development mode. Invalid credentials is thus just an error that is returned from axios, very likely, a 401 - Unauthorized error.
You would very likely get the same error when making the axios request without react-query.
I tried using redux to save token the one I get from api in react native ..its working now.
First one is for settoken and other one is for gettoken.
enter image description here
export const verifyOTP = (formValues, actions) => {
return async (dispatch) => {
dispatch(startSubmitting());
const url = `/validate-otp`;
var formdata = new FormData();
formdata.append("mobile", formValues.mobile);
formdata.append("otp", formValues.otp);
const response = await api.post(url, formdata);
dispatch({
type: "VERIFY_OTP",
payload: response,
});
dispatch(stopSubmitting());
await SecureStore.setItemAsync("userToken", response.data.access_token);
};
};
export const checkUser = () => {
return async (dispatch) => {
const token = await SecureStore.getItemAsync("userToken");
const url = `/me`;
const response = await api
.post(url, { token })
.then((res) => {
return res;
})
.catch((error) => {
return error.response;
});
dispatch({
type: "CHECK_USER",
payload: response,
});
};
};
The Problem
you are mixing two different implementations in checkUser to handle a promise which is clearly incorrect and leads to the issues.
The Solution
since your other parts of codes use the async/await so try to remove then/catch block from the response constant:
const checkUser = () => {
return async (dispatch) => {
const url = '/me';
try {
const token = await SecureStore.getItemAsycn("userToken);
const response = await api.post(url, {token})
dispatch({type: "CHECK_USER", payload: response})
} catch (error) {
// to proper action on failure case
}
}
}
Note 1: always use async/await in try/catch block. more on MDN documentation.
Optional
since you are trying to call two async actions (once for getting token and once for calling '/me' API), I encourage you to use two different try/catch blocks to handle the failure case for each async action separately. for example:
const checkUser = () => {
return async (dispatch) => {
let token = null;
try {
token = await SecureStore.getItemAsync("userToken");
} catch (err) {
// proper action in case of failure on getting the token from storage
}
// you may need to ignore API calls without the token, so:
try {
if(token){
const url = '/me';
const response = await api.post(url, {token});
dispatch({type: "CHECK_USER", payload: response});
}
} catch (err) {
// take proper action with the error response according to your applicaiton
}
}
}
Im using Auth0 to authenticate users.
Im protected api routes like this:
// pages/api/secret.js
import { withApiAuthRequired, getSession } from '#auth0/nextjs-auth0';
export default withApiAuthRequired(function ProtectedRoute(req, res) {
const session = getSession(req, res);
const data = { test: 'test' };
res.json({ data });
});
My problem is when I'm trying to fetch the data from getServerSideProps I'm getting 401 error code.
If I use useEffect Im able to get data from api route.
Im trying to fetch the data like this:
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret');
const data = await res.json();
return { props: { data } };
},
});
Im getting the following response:
error: "not_authenticated", description: "The user does not have an active session or is not authenticated"
Any idea guys? Thanks!!
When you call from getServerSideProps the protected API end-point you are not passing any user's context (such as Cookies) to the request, therefore, you are not authenticated.
When you call from useEffect it runs inside your browser, which attaches all cookies to the request, one of them is the session cookie.
You need to forward the session cookie that was passed to the getServerSideProps (by the browser) to the API call.
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret', {
headers: { Cookie: ctx.req.headers.cookie },
// ---------------------------^ this req is the browser request to the getServersideProps
});
const data = await res.json();
return { props: { data } };
},
});
For more info.
#auth0/nextjs-auth0 has useUser hook. This example is from: https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/
// pages/index.js
import { useUser } from '#auth0/nextjs-auth0';
export default () => {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (user) {
return (
<div>
Welcome {user.name}! Logout
</div>
);
}
// if not user
return Login;
};
Note that authentication takes place on the server in this model,
meaning that the client isn't aware that the user is logged in. The
useUser hook makes it aware by accessing that information in the
initial state or through the /api/auth/profile endpoint, but it won't
expose any id_token or access_token to the client. That information
remains on the server side.
Custom HOF:
// getData is a callback function
export const withAuth = (getData) => async ({req, res}) => {
const session = await auth0.getSession(req);
if (!session || !session.user) {
res.writeHead(302, {
Location: '/api/v1/login'
});
res.end();
return {props: {}};
}
const data = getData ? await getData({req, res}, session.user) : {};
return {props: {user: session.user, ...data}}
}
Example of using:
export const getServerSideProps = withAuth(async ({req, res}, user) => {
const title = await getTitle();
return title;
});
I try to implement axios interceptor, for my request.
So when the error response is forbidden (401), I want to clear token and navigate to login screen.
But when I want to call useNavigation from react navigation v5 inside my axios file, it always shows error like this.
React Hook "useNavigation" is called in function "refreshAccessToken" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter.
This is my axios file code,
import axios from 'axios'
import AsyncStorage from '#react-native-community/async-storage'
import {useNavigation} from '#react-navigation/native'
import {generateRandomString, getDeviceInfo} from '../helper/helper'
const base = axios.create({
baseURL: '------',
timeout: 500000,
headers: {
'Content-Type': 'application/json'
}
})
async function refreshAccessToken() {
const navigation = useNavigation()
try {
const refreshToken = await AsyncStorage.getItem('refreshToken')
const response = await base.get('------', {
refreshToken
})
await AsyncStorage.setItem('accessToken', response.data.accessToken)
console.log('Response from refresh access token ', response)
return Promise.resolve(response.data)
} catch (error) {
console.log('Error from refresh access token', error.response)
if (error.response.data.errors.flag === 'INVALID_REFRESH_TOKEN') {
await AsyncStorage.removeItem('refreshToken')
await AsyncStorage.removeItem('accessToken')
}
return Promise.reject(error)
}
}
base.interceptors.request.use(
async function (config) {
const accessToken = await AsyncStorage.getItem('accessToken')
config.headers.authorization = accessToken
const deviceInfo = await getDeviceInfo()
const latitude = await AsyncStorage.getItem('latitude')
const longitude = await AsyncStorage.getItem('longitude')
config.headers['accept-language'] = 'id-ID'
config.headers['version'] = '1.0.0'
config.headers['date'] = new Date().toUTCString()
config.headers['x-coordinate'] = `${latitude};${longitude}`
config.headers['x-trace-id'] = generateRandomString()
config.headers['x-device'] = `${deviceInfo.deviceType}/${deviceInfo.deviceName}/${deviceInfo.deviceVersion}/${deviceInfo.deviceUid}`
return config
},
function (error) {
return Promise.reject(error)
}
)
base.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (error.response.status === 403 && !originalRequest._retry) {
originalRequest._retry = true
const accessToken = await refreshAccessToken()
originalRequest.headers.Authorization = accessToken
return axios(originalRequest)
}
return Promise.reject(error)
}
)
export default base
You can't, hooks are only meant to be called inside components. There is a guide on how to navigate without the navigation prop you could use for this situation though.
You could also send an event (via a custom event emitter) and set up a listener anywhere else in your app where the navigation prop is available.
Have created React Native app and ned to use AsyncStorage to benefit from it's storage mechanism.
To save in AsyncStorage use code:
_storeData = async (param) => {
try {
let par = JSON.stringify(param);
//await AsyncStorage.setItem(this.key, par);
Utilities.setItem(this.key, par);
this._retrieveData();
} catch (error) {
console.log(JSON.stringify(error));
}
};
To retrieve data:
_retrieveData = async () => {
try {
const value = Utilities.getItem(this.key);
if (value !== null) {
alert('data is new: ' + JSON.stringify(value));
}
} catch (error) {
}
};
And, to setItem and getItem in Utilities partial:
const setItem = (key, value) => {
if (!key || !value) return;
AsyncStorage.setItem(key, value);
};
const getItem = (key) => {
if (!key) return;
var val = AsyncStorage.getItem(key);
return val;
};
Data is being saved, but response I'm getting does not look correctly, as it's a string of 'weird' characters:
{"_40":0,"_65":0,"_55":null,"_72":null}
Does anybody know why I'm getting such answer?
Note that AsyncStorage.getItem is also async - the weird characters represent the promise being returned by getItem.
Use var val = await AsyncStorage.getItem(key); and mark your getItem utility function as async. You'll need to await on any calls to Utilities.getItem and Utilities.setItem as well.