From 9adde8be34a144ecd59d55c0c7728141c23eb5dd Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 28 Feb 2018 13:30:54 +0000 Subject: [PATCH] [codorial] WIP authentication tutorial --- .../authentication-with-firebase/config.json | 21 ++ .../getting-started.md | 43 ++++ .../integrating-redux.md | 82 +++++++ .../project-structure.md | 124 +++++++++++ .../understanding-firebase-auth.md | 200 ++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 codorials/authentication-with-firebase/config.json create mode 100644 codorials/authentication-with-firebase/getting-started.md create mode 100644 codorials/authentication-with-firebase/integrating-redux.md create mode 100644 codorials/authentication-with-firebase/project-structure.md create mode 100644 codorials/authentication-with-firebase/understanding-firebase-auth.md diff --git a/codorials/authentication-with-firebase/config.json b/codorials/authentication-with-firebase/config.json new file mode 100644 index 00000000..e997db7c --- /dev/null +++ b/codorials/authentication-with-firebase/config.json @@ -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" + } + ] +} diff --git a/codorials/authentication-with-firebase/getting-started.md b/codorials/authentication-with-firebase/getting-started.md new file mode 100644 index 00000000..81fb50af --- /dev/null +++ b/codorials/authentication-with-firebase/getting-started.md @@ -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. + diff --git a/codorials/authentication-with-firebase/integrating-redux.md b/codorials/authentication-with-firebase/integrating-redux.md new file mode 100644 index 00000000..39950cc6 --- /dev/null +++ b/codorials/authentication-with-firebase/integrating-redux.md @@ -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 ( + + + + ); + } + } +} + +export default bootstrap; +``` + +Our app now has access to the power of Redux! diff --git a/codorials/authentication-with-firebase/project-structure.md b/codorials/authentication-with-firebase/project-structure.md new file mode 100644 index 00000000..1662f28b --- /dev/null +++ b/codorials/authentication-with-firebase/project-structure.md @@ -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 ( + + + Bootstrapped! + + + ); + } + } +} + +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 ( + + + Bootstrapped! + + + ); + } + +} + +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 ( + + ); + } + } +} + +export default bootstrap; +``` diff --git a/codorials/authentication-with-firebase/understanding-firebase-auth.md b/codorials/authentication-with-firebase/understanding-firebase-auth.md new file mode 100644 index 00000000..ac72984c --- /dev/null +++ b/codorials/authentication-with-firebase/understanding-firebase-auth.md @@ -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 ( + + Not signed in + + ); + } + + // Firebase has responded, and a user exists + return ( + + User signed in! {user.email} + + ); + } +} +``` + +### 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.