How to move to login to home by using async storage - react-native

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.

Related

Issue while trying to post / get an API on expo

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.

React-native login with API refresh HomeScreen after login with another user

After I log out with a user and then log in with another, the data of the last user persists in the main screen, I need for that Main screen to refresh after another user logs-in.
How is it possible to do that?
this is in my componentDidMount()
async componentDidMount() {
await Font.loadAsync({
'AbhayaLibre-Regular': require('../assets/fonts/AbhayaLibre-Regular.ttf'),
'AbhayaLibre-Bold': require('../assets/fonts/AbhayaLibre-Bold.ttf'),
});
this.setState({ fontLoaded: true });
this.authCheck()
if (this.state.loggedIn == "false") {
this.props.navigation.navigate("Login");
} else {
this.authUser();
this.fetchSubjects();
}
}
It puzzles me because I'm not sure how to run that code again AFTER a new user logs-in after another logged out.
Code snipped of the login:
<Button icon="send" mode="contained" text="#1ebc61" color="white" onPress={() => {
axios.post('http://homewrk.test/api/auth/login', {
email: this.state.email,
password: this.state.password
}).then(res => {
this.setState({ token: res.data.access_token });
this.storeKey();
this.props.navigation.navigate("Home");
}).catch(() => {
this.setState({ error: true })
})
}}>
authCheck()
async authCheck() {
const token = await AsyncStorage.getItem('access');
const access = 'Bearer ' + token;
console.log(access);
axios.get(`http://homewrk.test/api/check`, {
headers: {
'Authorization': access,
}
}).then(res => {
const isLoggedIn = res.data;
this.setState({ loggedIn: isLoggedIn });
})
}
storing the key:
storeKey = async () => {
try {
await AsyncStorage.setItem('access', this.state.token);
console.log("done");
} catch (e) {
console.log(e);
}
}
async componentDidMount() {
await Font.loadAsync({
'AbhayaLibre-Regular': require('../assets/fonts/AbhayaLibre-Regular.ttf'),
'AbhayaLibre-Bold': require('../assets/fonts/AbhayaLibre-Bold.ttf'),
});
this.focusListener = this.props.navigation.addListener("willFocus", () => {
this.authCheck()
if (this.state.loggedIn == "false") {
this.props.navigation.navigate("Login");
} else {
this.authUser();
this.fetchSubjects();
}
});
}
componentWillUnmount() {
this.focusListener.remove();
}

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.

Handling Refresh Token in React Native

I have an app authenticating fine and returning the access_token and refresh_token. I store them with AsyncStorage and save/get the access_token with redux. This is the very first app I am building and I am struggling with how and where to use the refresh_token.
This is the axios call in the component loginForm.js
axios({
url: `${base}/oauth/token`,
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
setStatus({ succeeded: true });
// console.log(response.data);
deviceStorage.saveKey("userToken", response.data.access_token);
deviceStorage.saveKey("refreshToken", response.data.refresh_token);
Actions.main();
})
.catch(error => {
if (error.response) {
console.log(error);
}
});
This is the service deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
This is the token action file
import { AsyncStorage } from 'react-native';
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../types';
export const getToken = token => ({
type: GET_TOKEN,
token,
});
export const saveToken = token => ({
type: SAVE_TOKEN,
token
});
export const removeToken = () => ({
type: REMOVE_TOKEN,
});
export const loading = bool => ({
type: LOADING_TOKEN,
isLoading: bool,
});
export const error = tokenError => ({
type: ERROR_TOKEN,
tokenError,
});
export const getUserToken = () => dispatch =>
AsyncStorage.getItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(getToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const saveUserToken = (data) => dispatch =>
AsyncStorage.setItem('userToken', data)
.then(() => {
dispatch(loading(false));
dispatch(saveToken('token saved'));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const removeUserToken = () => dispatch =>
AsyncStorage.removeItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(removeToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
This is the token reducer file
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../actions/types';
const INITIAL_STATE = {
token: {},
loading: true,
error: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TOKEN:
return {
...state,
token: action.token
};
case SAVE_TOKEN:
return {
...state,
token: action.token
};
case REMOVE_TOKEN:
return {
...state,
token: action.token
};
case LOADING_TOKEN:
return {
...state,
loading: action.isLoading
};
case ERROR_TOKEN:
return {
...state,
error: action.error
};
default:
return state;
}
};
And this is the authentication file
import React from 'react';
import {
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import { Spinner } from '../common';
import { getUserToken } from '../../actions';
class AuthLoadingScreen extends React.Component {
componentDidMount() {
this.bootstrapAsync();
}
bootstrapAsync = () => {
this.props.getUserToken().then(() => {
if (this.props.token.token !== null) {
Actions.main();
} else {
Actions.auth();
}
})
.catch(error => {
this.setState({ error });
});
};
render() {
return (
<View style={styles.container}>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
});
const mapStateToProps = state => ({
token: state.token,
});
const mapDispatchToProps = dispatch => ({
getUserToken: () => dispatch(getUserToken()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
I believe I need to create an action and reducer to get the refresh_token (is that correct?) but I do not know what to do with it and where to call it (perhaps in the authentication file?).
Any help with this possibly with code examples related to my code would be massively appreciated. Thanks
Below are the steps
Do Login , get accessToken , refreshToken from response and save it to AsyncStorage.
Make common function for API calling
async function makeRequest(method, url, params, type) {
const token = await AsyncStorage.getItem('access_token');
let options = {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
if (!token) {
delete options['Authorization'];
}
if (['GET', 'OPTIONS'].includes(method)) {
url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
} else {
Object.assign(options, {body: JSON.stringify(params)});
}
const response = fetch(ENV.API_URL+url, options);
return response;
}
Make one method in redux for getAceessTokenFromRefreshToken.
Use this method when session is expired
How do you know session is expired?
From each API calling if you get response like (440 response code) in
async componentWillReceiveProps(nextProps) {
if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) {
if (nextProps.followResponse.status) {
if (nextProps.followResponse.status == 440) {
// call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
}
}
}
}

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 />
}