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.
Related
I am currently programming a small login api and have implemented it with my react native project using axios request. But i have a problem now my server send an error status code with a message but in the react antive app it only comes to an exception with the status code and not with the message. How can I best solve this do I have to take the status code out of the server and just send text back or is there another solution?
My React Native api.js:
import axios from "axios";
import AsyncStorage from "#react-native-community/async-storage";
const instance = axios.create({
baseURL: "http://example.com",
});
instance.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(err) => {
return Promise.reject(err);
}
);
export default instance;
My React Native api trigger:
const Signup = async ({email, password}) => {
try{
const response = await myApi.post("/api/signup", { email, password });
if (response.data.token){
navigation.navigate('setup');
}else{
setError(response.data);
}
}catch (err){
console.log(err);
setError('Internet Error');
}
}
and my server response
return res.status(400).send({
message: "Email or password incorrect!",
});
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;
});
Hello I'm new to react native and I'm trying to navigate to Home Screen with a successful authentication, but this generates an error, can someone help me? please
loginWithFacebook = async() => {
await Facebook.initializeAsync(
'235487284376992',
);
const { type, token } = await Facebook.logInWithReadPermissionsAsync(
{ permissions: ['public_profile'] }
);
if (type === 'success') {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
this.props.navigation.navigate('Home')
firebase
.auth().signInWithCredential(credential).catch(error => {
console.log(error);
});
}
Firebase is working, however an error is generated during navigation
The error when try login
this error only happens after the first authentication, then the navigation normally occurs
Are you using react-native-firebase?
Which version are you using?
If you are using react-native-firebase v6+ , you need to install #react-native-firebase/auth module.
React Native Firebase version 6 has been re-created from the ground up.
And also, you must import the firebase from auth module on header
import {firebase} from "#react-native-firebase/auth";
This is my code.
import {
createTypes,
createAction,
transformNetworkError,
socialLoginError,
} from '../../utils/actions';
import {getWithData} from '../../utils/request';
import {OneSignalUtils} from '../../utils/oneSignalUtils';
import {FirebaseUtils} from '../../utils/firebaseServices';
import { LoginManager, AccessToken } from 'react-native-fbsdk';
import { GoogleSignin } from '#react-native-community/google-signin';
import {firebase} from "#react-native-firebase/auth";
const facebookLogin = () => async dispatch => {
const loginAction = {
do: () => createAction(LOGIN.DO, {}),
success: (response, authData) => createAction(LOGIN.SUCCESS, {userData: response, authData: authData}),
failed: (error) => createAction(LOGIN.FAILED, error),
};
try {
dispatch(loginAction.do());
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
if (result.isCancelled) {
dispatch(loginAction.failed(socialLoginError({status: true, message: 'Facebook login canceled.'})));
}
const logindata = await AccessToken.getCurrentAccessToken();
if (!logindata) {
dispatch(loginAction.failed(socialLoginError({status: true, message: 'Something went wrong obtaining access token.'})));
return null;
} else {
const credential = firebase.auth.FacebookAuthProvider.credential(logindata.accessToken);
const firebaseUserCredential = await firebase.auth().signInWithCredential(credential);
const data = firebaseUserCredential.user.toJSON();
await FirebaseUtils.userRef.doc(data.uid).set(data);
dispatch(loginAction.success());
return data;
}
} catch (e) {
console.log('facebook login error', e);
dispatch(loginAction.failed(socialLoginError({status: true, message: 'Something went wrong.'})));
return null;
}
};
i have a interceptor with axios but i don't know where to ubicate it. i attach javascript
import axios from 'axios';
import {getStoreData, checkStoreData} from './../utils/safestorage';
axios.interceptors.request.use(
async config => {
console.log('interceptor working');
const checkUser = await checkStoreData('user');
if (checkUser) {
const data = await getStoreData('user');
datajson = JSON.parse(data);
config.headers.Authorization = `Bearer ${datajson.access_token}`;
return config;
}
},
error => {
return Promise.reject(error);
},
);
I solved it importing the file in my app.js, an then my interceptors initialized.
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);
}
}