Issue while trying to post / get an API on expo - react-native

I'm new to react-native and it's my first app.
I'm trying to develop my app and connect it to my API. I develop all my app with the navigator view on Expo and there is no problem, the connection is good and I can get or post everything.
Now that I'm trying to fetch it with expo on my Android or Apple, there is no response.
Here is my code for the authentication:
login.js
import { post } from '../request/post';
export const login = (mail, pass) => {
console.log(mail)
console.log(pass)
console.log("POST request for login");
return post('/api/login', {
email: mail,
password: pass,
mobile: true
});
};
post.js
import { API_URL } from '../url';
import { getStorageToken } from '../../utils/asyncStorage';
const getHeaders = async () => {
const token = await getStorageToken();
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
};
if (token !== 'undefined' && token.length > 0) {
headers['auth'] = `${token}`;
}
return headers;
};
export const post = async (destination, body) => {
const headers = await getHeaders();
const result = await fetch(`${API_URL}${destination}`, {
method: 'POST',
headers,
body: JSON.stringify(body),
});
console.log(result);
if (result.ok) {
return await result.json();
}
throw { error: result.status };
};
loginPage.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView, Image, TextInput, Linking } from 'react-native';
import { setStorageAfterConnection } from '../../utils/asyncStorage';
import { CheckBox, Icon } from 'react-native-elements';
import { login } from '../../api/auth/login';
export default class LogIn extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errorMessage: ''
};
}
submit = () => {
login(this.state.email, this.state.password)
.then(async (res) => {
await setStorageAfterConnection(res);
this.props.navigation.navigate('Home');
})
.catch((res) => {
if (res && res.error) {
this.setState({ errorMessage: res.error});
}
this.setState({ errorMessage: "Erreur de connexion"});
});
};
render() {
return (
...............................................
);
}
}
I tried to debug it and it seems to not find the function post() because I don't have any network request. I do not know what's the correct way to do an "API" component so I think I probably made some mistakes but I didn't find what I'm missing.
I used Lan connection and my API isn't hosted on local.
Regards,

Try to add async-await to "login":
export const login = async (mail, pass) => { <---- 'async' ADDED
console.log(mail)
console.log(pass)
console.log("POST request for login");
return await post('/api/login', { <---- 'await' ADDED
email: mail,
password: pass,
mobile: true
});
};

I tried to put some debug on my code:
export const post = async (destination, body) => {
console.log("A");
const headers = await getHeaders();
console.log("B")
const result = await fetch(`${API_URL}${destination}`, {
method: 'POST',
headers,
body: JSON.stringify(body),
});
console.log(result);
if (result.ok) {
return await result.json();
}
throw { error: result.status };
};
And I get on the console:
email
password
POST request for login
A
So the problem seems to be on my await getHeaders()
EDIT: Problem was solved. It was because of the getHeaders that try to get the token and failed.

Related

Show user info on login - react native

I have my login working, it logs me in without problems and it enters the screen that I want, but when I want to show the information on the screen of the user that logs in, I have not been able to show the user information on the screen that it should load from database. I have tried to create an interface where I add the names of the fields as they are in the database but when calling them from my screen nothing is loaded
what I need is that when I log in to my screen protectedScreen.ts it shows me the data of the user that logs in
mi db: Tabla : usuarios campos: id, name, email, password, tipo_usuario
AuthContext.ts //where valid and login
import React,{ createContext, useEffect, useReducer } from "react";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { SafeAreaView, StyleSheet, TextInput, View, Alert, TouchableOpacity, Text } from "react-native";
import cafeApi from "../api/cafeApi";
import { LoginData, LoginResponse, RegisterData, Usuarios } from "../interfaces/appinterfaces";
import { authReducer, AuthState } from "./AuthReducer";
type AuthContextProps = {
errorMessage: string;
token: string | null;
user: Usuarios | null;
status: 'checking' | 'authenticated' | 'not-authenticated';
signUp: (RegisterData: RegisterData) => void;
signIn: (loginData : LoginData) => void;
logOut: () => void;
removeError: () => void;
}
const authInicialState: AuthState = {
status:'checking',
token: null,
user: null,
errorMessage:''
}
export const AuthContext = createContext({} as AuthContextProps);
export const AuthProvider = ({children}: any) =>{
const[state, dispatch] = useReducer(authReducer,authInicialState);
useEffect(() =>{
checkToken();
}, [])
const checkToken = async () =>{
const token = await AsyncStorage.getItem('token');
//No token, no autenticado
if (!token) return dispatch({ type:'notAuthenticated'});
//hay token
const resp = await cafeApi.get('/usuarios/middlewares/Auth.php');
if(resp.status !== 200){
return dispatch({type:'notAuthenticated'});
}
await AsyncStorage.setItem('token', resp.data.token)
dispatch({
type: 'signUp',
payload:{
token: resp.data.token,
user: resp.data.usuarios
}
})
}
const signIn = async({correo, password}: LoginData) => {
try {
await fetch('https://www.miweb.com/apiPlooy/usuarios/login.php',
{
method:'POST',
headers:{
'Accept': 'application/json',
'content-Type': 'application/json'
},
body: JSON.stringify({"email":correo, "password" : password})
}).then(res => res.json())
.then(resData => {
if(resData.message == "Ha iniciado sesiĆ³n correctamente.") {
dispatch({
type: 'signUp',
payload: {
token: resData.token,
user: resData.usuarios
}
});
}else{
Alert.alert(resData.message)
}
});
}catch (error) {
dispatch({type: 'addError',
payload: error.response.data.msg || 'InformaciĆ³n incorrecta'})
}
};
return(
<AuthContext.Provider value={{
...state,
signUp,
signIn,
logOut,
removeError,
}}>
{children}
</AuthContext.Provider>
)
}
appinterfaces.ts // where I add the user db fields to be able to show them on the screen when I login
export interface LoginData{
correo: string;
password: string;
}
export interface LoginResponse{
usuarios: Usuarios;
token: string;
}
export interface Usuarios{
tipo_usuario: String;
email: String;
password: String;
name: String
}
ProtectedScreen.ts // This is my screen when I log in and where I want to show some field of my user that I log in but it is not shown.
interface Props extends StackScreenProps<any, any>{}
export const ProtectedScreen = ({navigation}: Props) => {
const {user, token} = useContext(AuthContext);
const {email, password, name, onChange} = useForm({
email:'',
password:'',
name:'',
});
return (
<>
<View style={loginStyles.formContainer}>
<Text>BIENVENIDO
Tipo usuario: {JSON.stringify(user.tipo_usuario, null, 50)}
</Text>
</View>
</>
)
}
code and api: https://github.com/Giovannychvz/react-native
//I add what I have found in case it is of any use I have a project in reactjs and it uses the same api as the react native project and when I log in it loads the data of the user who logged in, I don't know if it is of any use but I send the context of react:
Note: the only strange thing I found in this context of react is that user-info.php is being called and I am not calling this file from react native because the problem must be there but I have not been able to solve it.
context.js
import React, { createContext,Component } from "react";
import axios from 'axios'
import history from '../components/history';
export const MyContext = createContext();
// Define the base URL
const Axios = axios.create({
baseURL: 'https://www.miweb.com/apiPlooy/usuarios/',
});
class MyContextProvider extends Component{
constructor(){
super();
this.isLoggedIn();
history.push('/');
}
// Root State
state = {
showLogin:true,
isAuth:false,
theUser:null,
}
// Toggle between Login & Signup page
toggleNav = () => {
const showLogin = !this.state.showLogin;
this.setState({
...this.state,
showLogin
})
}
// On Click the Log out button
logoutUser = () => {
localStorage.removeItem('loginToken');
localStorage.clear();
history.push('/');
this.setState({
...this.state,
isAuth:false
})
}
registerUser = async (user) => {
// Sending the user registration request
const register = await Axios.post('register.php',{
name:user.name,
email:user.email,
password:user.password
});
return register.data;
}
loginUser = async (user) => {
// Sending the user Login request
const login = await Axios.post('login.php',{
email:user.email,
password:user.password
});
return login.data;
}
// Checking user logged in or not
isLoggedIn = async () => {
const loginToken = localStorage.getItem('loginToken');
// If inside the local-storage has the JWT token
if(loginToken){
//Adding JWT token to axios default header
Axios.defaults.headers.common['Authorization'] = 'bearer '+loginToken;
// Fetching the user information
const {data} = await Axios.get('user-info.php');
// If user information is successfully received
if(data.success && data.user){
this.setState({
...this.state,
isAuth:true,
theUser:data.user
});
}
}
}
render(){
const contextValue = {
rootState:this.state,
toggleNav:this.toggleNav,
isLoggedIn:this.isLoggedIn,
registerUser:this.registerUser,
loginUser:this.loginUser,
logoutUser:this.logoutUser
}
return(
<MyContext.Provider value={contextValue}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyContextProvider;

apiplatform - react admin: token in localstorage null on redirect, have to refresh page

I'm using the stack apiplatform and react admin.
My JWT authentification works fine on apiplatform.
I try to use it on my react admin backoffice.
I followed those documentations:
https://api-platform.com/docs/admin/authentication-support/
https://marmelab.com/react-admin/doc/2.9/Authentication.html
The authentification/authorization works but on login success, I am redirected to the backoffice and I have a server communication error because the token send to the api is null.
I see it in the localstorage and if I refresh the page, everything works fine.
It's like the redirection on success happen before the token was store.
Here is my code:
App.js
import React from "react";
import { HydraAdmin, ResourceGuesser } from "#api-platform/admin";
import authProvider from "./components/authProvider";
import parseHydraDocumentation from "#api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation";
import {
dataProvider as baseDataProvider,
fetchHydra as baseFetchHydra
} from "#api-platform/admin";
import { Redirect } from "react-router-dom";
const entrypoint = "http://localhost:8089/api";
const fetchHeaders = {
Authorization: `Bearer ${window.localStorage.getItem("token")}`
};
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders)
});
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders)
}).then(
({ api }) => ({ api }),
result => {
switch (result.status) {
case 401:
return Promise.resolve({
api: result.api,
customRoutes: [
{
props: {
path: "/",
render: () => <Redirect to={`/login`} />
}
}
]
});
default:
return Promise.reject(result);
}
}
);
const dataProvider = baseDataProvider(
entrypoint,
fetchHydra,
apiDocumentationParser
);
export default () => (
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
dataProvider={dataProvider}
authProvider={authProvider}
entrypoint={entrypoint}
>
<ResourceGuesser name="resource" />
</HydraAdmin>
);
authProvider.js
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_CHECK, AUTH_ERROR } from "react-admin";
export default (type, params) => {
if (type === AUTH_LOGIN) {
const { email, password } = params;
const request = new Request("http://localhost:8089/api/login_check", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: new Headers({ "Content-Type": "application/json" })
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem("token", token);
});
}
if (type === AUTH_LOGOUT) {
localStorage.removeItem("token");
return Promise.resolve();
}
if (type === AUTH_ERROR) {
console.log("AUTH_ERROR");
//localStorage.removeItem("token");
return Promise.resolve();
}
if (type === AUTH_CHECK) {
return localStorage.getItem("token")
? Promise.resolve()
: Promise.reject({ redirectTo: "/login" });
}
return Promise.resolve();
};
I don't know if it's the right solution because like you, I didn't find anything about this problem.
But for me, if I just call window.location.reload(); after localStorage.setItem('token', token); it solves the problem for me, because after login, it reloads admin and at that moment, it can recognize token. Maybe it's not the cleanest solution ever but it works well.
By the way, I think, this is not a problem related with the HydraAdmin component, I tried the classic React Admin component and the problem is still there, so it's related to React Admin.

How can I properly test my React Native OAuth wrapper component?

I have written a React Native "Auth Portal" component, that links with an existing OAuth portal and handles getting the auth-code from the redirect URI and the subsequent token exchange request. It seems to be working well, but clearly I need to test this assumption, so I am trying to write unit/functional tests. How can I properly do this?
I originally considered extracting the functions used in the two useEffects out into separate, isolated functions and taking, for example, the authCode as an argument instead of from state and mocking this input.
However, I believe a better strategy is to test the component as a whole and just mock the response to the axios post request, comparing that mock to what get's stored in the AsyncStorage, as well as mocking a bad request/response to test the error handling.
Is this a good approach?
import axios from 'axios'
import AsyncStorage from '#react-native-community/async-storage'
import React, { useEffect, useState } from 'react'
import { Linking } from 'react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import { LoadingIndicator } from '../LoadingIndicator'
interface AuthPortalProps {
client_id: string
scopes: string[]
client_secret: string
redirect_uri: string
onAuthComplete: () => void
onError: () => void
}
interface ApiDataResponse {
token_type: string
expires_in: number
access_token: string
refresh_token: string
}
export const AuthPortal = ({
client_id,
scopes,
client_secret,
redirect_uri,
onAuthComplete,
onError,
}: AuthPortalProps) => {
const [authCode, setAuthCode] = useState()
const getAuthCodeFromRedirectUri = async (url: string) => {
if (url.includes('code=')) {
const regex = /[^=]+$/g
const code = url.match(regex)!.toString()
await setAuthCode(code)
}
}
useEffect(() => {
const getAuthCode = async () => {
const url = `https://example.com/auth/?response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${scopes}`
if (!authCode) {
try {
InAppBrowser.openAuth(url, redirect_uri).then(response => {
if (response.type === 'success' && response.url && response.url.includes('code=')) {
getAuthCodeFromRedirectUri(response.url)
Linking.openURL(redirect_uri)
}
})
} catch (error) {
console.log('Error: ', error.message)
onError()
}
}
}
getAuthCode()
return () => {
InAppBrowser.closeAuth()
}
}, [authCode, client_id, onError, redirect_uri, scopes])
useEffect(() => {
const getAuthRefreshToken = async () => {
if (authCode) {
try {
const { data }: { data: ApiDataResponse } = await axios.post(
'https://example.com/auth',
{
grant_type: 'authorization_code',
client_id: `${client_id}`,
code: `${authCode}`,
client_secret: `${client_secret}`,
redirect_uri: `${redirect_uri}`,
}
)
await Promise.all([
AsyncStorage.setItem('access_token', data.access_token),
AsyncStorage.setItem('refresh_token', data.refresh_token),
])
setTimeout(() => {
onAuthComplete()
}, 1000)
} catch (error) {
if (error.response) {
console.log('Error: ', error.response)
} else if (error.request) {
console.log('Error: ', error.request)
} else {
console.log('Error: ', error.message)
}
onError()
}
}
}
getAuthRefreshToken()
}, [authCode, client_id, client_secret, onAuthComplete, onError, redirect_uri])
return <LoadingIndicator />
}

How to move to login to home by using async storage

i have written code for setItem and getItem using async storage then i stored in to access token.now i want to check if user already login in to home page,then if we remove app from recents.if we open app then directly i has to move to home page.
app.js
import React from 'react';
import { createStackNavigator ,createAppContainer } from 'react-navigation';
import Homescreen from './components/Homescreen';
import Loginscreen from './components/Loginscreen'
import Forgetpassword from './components/Forgotpassword';
const Navstack = createStackNavigator({
// Home: Homescreen,
// Login:Loginscreen
Login : { screen : Loginscreen},
Home : { screen: Homescreen },
Password :{ screen: Forgetpassword}
});
const App = createAppContainer(Navstack);
export default App;
login.js:
//storing response in accesstoken
storeToken(responseData)
{
AsyncStorage.setItem(ACCESS_TOKEN, responseData, (err)=> {
if(err){
console.log("an error");
throw err;
}
console.log("success");
}).catch((err)=> {
console.log("error is: " + err);
});
}
home.js:
async getToken() {
try {
let accessToken = await AsyncStorage.getItem(ACCESS_TOKEN);
if(!accessToken) {
this.props.navigation.navigate('Login');
// console.warn(accessToken);
} else {
this.setState({accessToken: accessToken})
console.warn(accessToken);
}
} catch(error) {
console.log("Something went wrong");
// console.warn("Something went wrong");
this.props.navigation.navigate('Login');
}
}
Try this
login.js:
fetch("YOUR URL", {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"client_id": client_id,
"username": email,
"password": pass,
"grant_type": "password"
}),
}).then((response) => response.json())
.then(async(response) => {
this.setState({isLoading: false});
if(response.status == "success"){
navigate("Home");
await AsyncStorage.setItem('isLogin', 'true');
}else{
alert(JSON.stringify(response))
}
})
.catch((error) => {
alert(error);
});
And I am using splash screen before login then add
splash.js:
async componentDidMount(){
const isLogin = await AsyncStorage.getItem('isLogin');
setTimeout(() => {
this.setState({ isLoading: false })
const { navigate } = this.props.navigation;
if(isLogin != null || isLogin == "true")
navigate("Home");
}else{
navigate("Login");
}
},2000);
}
I could not able to understand your code so i post my code so hope this will help you.

Showing alert from service class in react-native

I have an HTTP service class which is responsible for communication with my Laravel based API. I want the HTTP service class to trigger React Native Alert box when there is a 401 authentication issue. Or 403 Access denied issue or 422 which is validation issue.
I have the basic HTTP service class ready, but I am not able to show an alert from my HTTP service because it is not a react component and I want to know if I can use such a global service class and still trigger Native components.
Below is my code:
import axios from 'axios';
import {
AsyncStorage,
Alert
} from 'react-native';
class HttpService {
async get(url) {
let at = await AsyncStorage.getItem('access_token', (error, accessToken) => {
return accessToken;
});
let data = await axios.get(url, {headers: { 'Authorization': 'Bearer ' + at }})
.then(response => {
console.log('response', response);
return response;
})
.catch(error => {
let message, title = '';
if (!error.response) {
message = error;
}
if (error.response.status === 401) {
message = error.response.data.message;
title = 'Authentication issue'
}
Alert.alert(title, message, [
{ text: 'Ok', onPress: () => console.log(123) }
]);
});
return data;
}
post() {}
}
export default HttpService;
Why not just return the errors too? So you can determine what occurred so the component that makes the HttpService call knows when to show the Alert.
class yourComponent extends Component{
constructor() {
this.state = {
token: null,
isLoaded: false,
error: null
}
}
componentDidMount() {
HttpService.get('someUrl')
.then(res => {
this.setState({
token: res,
isLoaded: true
});
}, (error) => {
this.setState({
error: error,
isLoaded: true
});
})
}
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
// return your Alert here
} else if (!isLoaded) {
// return loading component;
} else {
// return your main component
}
}
}