[codorial] WIP authentication tutorial

This commit is contained in:
Elliot Hesp 2018-02-28 13:30:54 +00:00
parent e73812a1e0
commit 9adde8be34
5 changed files with 470 additions and 0 deletions

View File

@ -0,0 +1,21 @@
{
"title": "Authentication with Firebase",
"steps": [
{
"title": "Getting Started",
"file": "getting-started"
},
{
"title": "Project Structure",
"file": "project-structure"
},
{
"title": "Understanding Firebase Auth",
"file": "understanding-firebase-auth"
},
{
"title": "Integrating Redux",
"file": "integrating-redux"
}
]
}

View File

@ -0,0 +1,43 @@
# Getting Started
Welcome to the 'Authentication with Firebase' Codorial using the [react-native-firebase](https://rnfirebase.io) library.
The various steps of the Codorial will cover how to setup your application to require both email/password login and social login using Facebook,
to handling the users authenticated state using the popular [redux](https://redux.js.org/introduction) library whilst also considering integrate routing
using [react-navigation](https://reactnavigation.org/).
## Prerequisites
This Codorial assumes you know the basics of the following topics:
- ES6 JavaScript.
- Starting your app using an emulator on Android/iOS.
- Understand how to setup a new Firebase project.
- Debugging with React Native.
- Managing your project using Android Stuido and/or XCode.
- Installation of the [react-native-firebase](https://rnfirebase.io) library (see "Creating a base project" below).
This Codorial was created with React Native version `0.53.0`.
## Creating a base project
This project will take a bare bones React Native setup and explain every step required to implement a solid authentication flow in your application.
To start we need a base project to work from.
Both options below require you to setup a new Firebase project and add the configuration file to your project - check out the [documentation](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) on how to do that if needed.
### Option 1: Using `react-native init`
You can quickly create a base React Native project using `react-native init` by following the React Native [documentation](http://facebook.github.io/react-native/docs/getting-started.html).
> Ensure you follow the "Building Projects with Native Code" tab, as the project won't work using Expo due to requiring native modules.
Once installed, you need to install the [react-native-firebase](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) library. Ensure you've
also installed the Authentication module on your platform ([Android](https://rnfirebase.io/docs/v3.2.x/auth/android) or [iOS](https://rnfirebase.io/docs/v3.2.x/auth/ios))!
### Option 2: Using [react-native-firebase-starter](https://github.com/invertase/react-native-firebase-starter)
A starter kit has been created to help you get up and running with minimal setup needed. If you're new to React Native this will be perfect starting point.
> Keep in mind every Firebase module is installed in this starter kit. You can refer to the react-native-firebase [documentation](https://rnfirebase.io/docs) if you want to remove
any unwanted modules.

View File

@ -0,0 +1,82 @@
# Integrating Redux
Redux has become somewhat of a buzz word in the React community, and is generally used in most projects without thought. This Codorial
won't go into details on what it is as their own [documentation](https://redux.js.org/introduction/motivation) does a wonderful job at explaining
what it's for and why to use it.
*TLDR* Redux provides your app with a single "state" (data), which can be accessed by any component. You can subscribe to this data to cause
a component update whenever something changes, even if it's deeply nested.
Although the end product of this Codorial certain doesn't require Redux to function, as your app grows in complexity Redux becomes more and
more important to manage your data.
## Installing Redux
Lets go ahead by installing the core Redux library and the React bindings:
```
npm install --save redux react-redux
```
Now within our projects `src` directory, create a `store.js` file. This file will contain all of our Redux logic, however you may want to break
this out into multiple directories as your projects grows in complexity.
```
// src/store.js
import { createStore } from 'redux';
// Create a reducer (see below for explanation)
function reducer(state, action) {
return state;
}
export default createStore(reducer);
```
> You may want to consider installing the [redux-logger](https://github.com/evgenyrodionov/redux-logger) library.
### Reducer
A reducer is a simple JavaScript function which takes two arguments: `state` & `action`. The idea of a reducer is to take "some data" from an `action`
and return new state.
- `state` is any sort of data which cannot be altered (immutable). A reducer must return a new value each time.
- `action` is an object containing a `type`, and any unreduced data. More on this later.
## Integrating Redux into the app
Our Redux store is now ready to be used. `react-redux` provides us with a `Provider` component which "provides" any children
with access to the store via [context](https://reactjs.org/docs/context.html). Luckily we don't need to worry about this too much as the lirbary
takes care of the hard work!
Back within our original bootstrap file, we'll wrap the `App` component in the `Provider` component, so our business logic has access to Redux.
```js
// src/index.js
import React, { Component } from 'react';
import { Provider } from 'react-redux'; // Import the Provider component
import App from './App';
import store from './store';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}
}
export default bootstrap;
```
Our app now has access to the power of Redux!

View File

@ -0,0 +1,124 @@
# Project Structure
Although it may seem trivial, having a good initial project structure ensures you code will be clean and reusable. The following step gives
an opinionated guide to how this might look, which will work across both Android & iOS.
## Entry file
Every fresh React Native project contains to key files, an `index.android.js` & a `index.ios.js` files which currently individually render a simple React component
with basic styling. Rather than having two separate files, we're going to create a single file so both Android & iOS use it.
We'll achieve this by creating a `src` directory where our own code for the app will live. Create the directory with an `index.js` file, so your
project structure resembles the following:
```
- node_modules/
- android/
- ios/
- src/
-- index.js
- index.js
```
Now we can reference our bootstrap file in the `index.js` file, so both our platform share the same entry point:
```js
// index.js
import { AppRegistry } from 'react-native';
import bootstrap from './src';
AppRegistry.registerComponent('RNFirebaseStarter', () => bootstrap());
```
## Bootstrapping your project
You may have noticed before, but the `bootstrap` import is a function. This allows us to setup or initialize any external modules before our
React based application kick starts (such as [react-native-i18n](https://github.com/AlexanderZaytsev/react-native-i18n)).
Lets go ahead and setup our bootstrap file:
```js
// src/index.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return (
<View>
<Text>
Bootstrapped!
</Text>
</View>
);
}
}
}
export default bootstrap;
```
Although this function simply returns a basic React component, later we'll be able to see the power of having a bootstrap file which
consumes our entire application.
Go ahead and boot up your app onto your emulator. You should simply be presented with a plain screen with the words "Bootstrapped!".
TODO image
Although a good starting point, we want to separate we'll our business logic out of the bootstrap file, keeping it purely for app
initialization purposes. This can simply be done by creating a basic React component called `App.js`, which will also live in the `src` directory;
```js
// src/App.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class App extends Component {
render() {
return (
<View>
<Text>
Bootstrapped!
</Text>
</View>
);
}
}
export default App;
```
Now we can reference this component within our bootstrap setup and return it from the bootstrap component:
```js
// src/index.js
import React, { Component } from 'react';
import App from './App';
function bootstrap() {
// Init any external libraries here!
return class extends Component {
render() {
return (
<App />
);
}
}
}
export default bootstrap;
```

View File

@ -0,0 +1,200 @@
# Understanding Firebase Authentication
Before we dive into the logic of implementing authentication, it's first important to understand how the Firebase API, and how it handles authentication
with the various options we have.
As we're also working in React, it's important to understand how Firebase's asynchronous API fits in with Reacts many lifecycle methods.
Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web SDK API making this a breeze!
## Enabling authentication
Before we make a start, we need to tell Firebase that we plan on using authentication. We need to also enable a couple of the many login providers
which Firebase supports. Head over to the [Firebase console](https://console.firebase.google.com/u/0/) and select the project you're using.
Find the Authentication section and you'll be prompted with a number of options. To get started, we want to select the "SIGN-IN METHOD" tab:
TODO image
You'll see we have a number of options here, however for purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers.
Go ahead and enable these:
TODO image
> If you don't have a Facebook app, simply enter dummy values. We'll cover this later on.
## Listening to the users authentication state
The Firebase API provides a simple yet powerful listener, which triggers when some event changes with the user.
This can be as obvious the user signing out or as subtle as the user validating their email address. Whatever the event, it triggers the same method: `onAuthStateChanged`.
```js
import firebase from 'react-native-firebase';
firebase.auth().onAuthStateChanged((user) => {
console.log(user);
});
```
The callback for the `onAuthStateChanged` method returns a single parameter, commonly referred to as `user`.
The concept here is simple;
- if a user is "signed in", our parameter will be a `class`, containing all sorts of information we know about the user,
from their e-mail address to any social provider IDs they may have signed in through.
- if the user signed out, the parameter will be `null` value.
- the method is immediately triggered when called.
> The `user` class provides a `.toJSON()` method to serialize the users details if required.
### Handling authentication state when the app closes
A common question we get is how to handle the users authenticated state when the app closes/restarts so they don't have to keep logging in each
time they open the app. Luckily this is all handled through Firebase so you don't have to worry about a thing - they'll only be signed out if they
choose to, or the app is uninstalled.
## Creating a new account
Creating a new account on Firebase is very easy. Another method called `createUserWithEmailAndPassword` is available which does exactly what it
says on the tin! This is an asynchronous promise which will throw an exception if something is wrong (such as email taken, or password too short).
Creating a user will also sign them in at the same time.
```js
import firebase from 'react-native-firebase';
firebase.auth().createUserWithEmailAndPassword('jim.bob@gmail.com', 'supersecret!')
.then((user) => {
console.log('New User', user);
})
.catch((error) => {
console.error('Woops, something went wrong!, error);
});
```
What's great about this is we don't need to know about the user within the `then`, as any `onAuthStateChanged` listener would get triggered with our new
users details - how awesome is that.
## Signing into an existing account
Unsurprisingly, Firebase offers a method called `signInWithEmailAndPassword`, which follows the exact same flow as `createUserWithEmailAndPassword`:
```js
import firebase from 'react-native-firebase';
firebase.auth().signInWithEmailAndPassword('jim.bob@gmail.com', 'supersecret!')
.then((user) => {
console.log('Existing User', user);
})
.catch((error) => {
console.error('Woops, something went wrong!, error);
});
```
## Using with React
Firebase on it's own is super simple, however when using in a React environment there's some gotchas you need to be mindful of.
### Handling state changes
For any React component to update, a state or prop change needs to occur. As our Firebase auth methods are asynchronous we cannot rely on
the data being available on component mount. To solve this issue, we can make use of state:
```js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import firebase from 'react-native-firebase';
class App extends React.Component {
constructor() {
super();
this.state = {
loading: false,
user: null,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({
user: user.toJSON(), // serialize the user class
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
}
render() {
const { loading, user } = this.state;
// Firebase hasn't responded yet
if (loading) return null;
// Firebase has responded, but no user exists
if (!user) {
return (
<View>
<Text>Not signed in</Text>
</View>
);
}
// Firebase has responded, and a user exists
return (
<View>
<Text>User signed in! {user.email}</Text>
</View>
);
}
}
```
### Subscribing/Un-subscribing from listeners
When subscribing to a new listener, such as `onAuthStateChanged`, a new reference to it is made in memory which has no knowledge of the
React environment. If a component within your app mounts and subscribes, the method will still trigger even if your component unmounted.
If this happens and you're updating state, you'll get a yellow box warning.
To get around this, Firebase returns an unsubscribe function to every subscriber method, which when calls removes the subscription.
This can be easily implemented using React lifecycle methods and class properties:
```js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import firebase from 'react-native-firebase';
class App extends React.Component {
constructor() {
super();
this.unsubscribe = null; // Set a empty class method
this.state = {
loading: false,
user: null,
};
}
componentDidMount() {
// Assign the class method to the unsubscriber response
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
// handle state changes
});
}
componentWillUnmount() {
// Call the unsubscriber if it has been set
if (this.unsubscribe) {
this.unsubscribe();
}
}
}
```
## Further reading
The above examples just scratch the surface of whats available with Firebase auth. Firebase itself provides some in-depth documentation
on authentication and the many different implementation paths you can follow.