Setting up an Authentication Flow in React Native

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

Last Updated: February 27, 2019

Hey - React Navigation has a new API! To read an updated article please check out the new tutorial.

Whether or not you use React Navigation you should be able to apply this framework to your authentication needs.

Ensuring proper authentication is critical and when things are critical I like to keep them as simple as possible.

When a user opens the app, here's what happens:

  • They land on an entry point file. This file will check if they're a authorized user.
  • If they're an authenticated user, I switch to the app state
  • If they're not an authenticated user, I switch to the auth state

That means I've got three app states:

  1. User status unknown
  2. User status known, authenticated
  3. User status known, not authenticated

Question: How do you handle if a user becomes logged out while using the app? What if they're authenticated but they're not authorized to do something (like access a pro feature)?


Here's the final code from the video:

App.js

import React from 'react';
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Button,
  AsyncStorage,
} from 'react-native';
import {
  createAppContainer,
  createStackNavigator,
  createBottomTabNavigator,
  createSwitchNavigator,
} from 'react-navigation';

const setUserId = (userId) => {
  if (userId) {
    return AsyncStorage.setItem('userId', userId);
  }

  return AsyncStorage.removeItem('userId');
};
const getUserId = () => AsyncStorage.getItem('userId');

/*
 * ####################
 * START APP
 * ####################
 */

class Home extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      backgroundColor: this.generateColor(),
    };
  }

  generateColor = () => {
    let backgroundColor = Math.floor(Math.random() * 16777215).toString(16);
    return '#' + ('000000' + backgroundColor).slice(-6);
  };

  render() {
    return (
      <View
        style={{
          flex: 1,
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: this.state.backgroundColor,
        }}
      >
        <Text
          style={{
            color: '#fff',
            fontWeight: 'bold',
            fontSize: 25,
          }}
        >
          {this.state.backgroundColor}
        </Text>
        <TouchableOpacity
          onPress={() => {
            this.setState({ backgroundColor: this.generateColor() });
          }}
          style={{
            backgroundColor: '#fff',
            marginTop: 20,
            paddingVertical: 10,
            paddingHorizontal: 20,
          }}
        >
          <Text>New Color!</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const Profile = ({ navigation }) => (
  <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Button
      title="Sign Out"
      onPress={async () => {
        await setUserId(null);
        navigation.navigate('Auth');
      }}
    />
  </View>
);

/*
 * ####################
 * START AUTH
 * ####################
 */

const SignIn = ({ navigation }) => (
  <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Button
      title="Sign In"
      onPress={async () => {
        // Wait on the server side sign in process...
        await setUserId('123');
        navigation.navigate('App');
      }}
    />
    <Button title="Sign Up" onPress={() => navigation.navigate('SignUp')} />
  </View>
);

const SignUp = ({ navigation }) => (
  <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Button
      title="Sign Up"
      onPress={async () => {
        // Wait on the server side sign up process...
        await setUserId('123');
        navigation.navigate('App');
      }}
    />
  </View>
);

/*
 * ####################
 * START NAVIGATION
 * ####################
 */

const App = createStackNavigator(
  {
    Tabs: createBottomTabNavigator({
      Home: {
        screen: Home,
      },
      Profile: {
        screen: Profile,
      },
    }),
  },
  {
    mode: 'modal',
    headerMode: 'none',
  }
);

const Auth = createStackNavigator({
  SignIn: {
    screen: SignIn,
    navigationOptions: {
      headerTitle: 'Sign In',
    },
  },
  SignUp: {
    screen: SignUp,
    navigationOptions: {
      headerTitle: 'Sign Up',
    },
  },
});

class Entry extends React.Component {
  async componentDidMount() {
    const userId = await getUserId();
    if (userId) {
      this.props.navigation.navigate('App', {});
    } else {
      this.props.navigation.navigate('Auth');
    }
  }

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Loading...</Text>
      </View>
    );
  }
}

const RootNavigation = createSwitchNavigator({
  Entry: {
    screen: Entry,
  },
  App: {
    screen: App,
  },
  Auth: {
    screen: Auth,
  },
});

export default createAppContainer(RootNavigation);
React Native School Logo

React Native School

Want to further level up as a React Native developer? Join React Native School! You'll get access to all of our classes and our private Slack community.