将Firebase认证整合到世博会的移动应用中

439 阅读14分钟

在本教程中,我们将用Expo建立一个与Firebase认证模块集成的移动应用。到最后,我们将拥有一个运行中的移动应用程序,具有以下功能。

  • 注册和登录屏幕(使用电子邮件和密码)
  • 用户的主屏幕只限于登录的用户
  • 使用React Navigation的动态屏幕路由

本教程的源代码可以在GitHub上找到。可以通过下面的链接自由跳转到你最感兴趣的部分。

用Expo创建一个React Native应用

在我们开始与Firebase集成之前,我们需要用Expo CLI来设置我们的项目。

如果你的系统中没有安装它,你将不得不运行。

npm install --global expo-cli

一旦你安装了它,你就可以用以下命令创建一个新的项目。注意,我们的项目名称是expo-with-firebase-auth-demo ,但你可以用不同的名称。

expo init expo-with-firebase-auth-demo

在项目准备就绪之前,Expo会要求你提供你想要的模板,并会提供以下选项。

Expo Template

就个人而言,我总是选择blank (TypeScript) 。你可以选择blank ,当然,但为什么不试试TypeScript呢?它很厉害。

在做出选择后,你将准备继续安装我们将需要的额外软件包。

对于那些额外的库,运行。

expo install @react-navigation/native @react-navigation/stack firebase dotenv react-native-elements expo-constants

注意,我们特意安装了包firebase ,也就是JavaScript SDK,而不是react-native-firebase 。这是由于该库在Expo Go应用中的限制。你可以在Expo官方文档Firebase文档中阅读更多相关内容。

接下来,我们将在Firebase上设置云项目。

在Firebase上设置云项目

要开始的话,你需要一个Firebase项目。如果你还没有一个Firebase账户,请访问firebase.google.com/,然后创建一个。之后,登录到你的Firebase控制台,用 "创建项目 "按钮创建一个新的项目。

Create Firebase Project

接下来,添加新的Firebase项目的名称。注意,它不一定要和世博项目的名称一样,然后点击继续

Add Project Name

在这一步,你可以决定是否要选择加入谷歌分析。尽管它为项目提供了一些有趣的功能,但我们的演示并不需要它,所以我将禁用它。

一旦完成,点击创建项目

Google Analytics Note

一旦进入你的项目界面,你需要设置一个应用程序。记住,一个Firebase项目可以承载多个应用,包括网站、unity和移动应用。

点击Web项目应用来创建一个新的Web项目。我们必须选择Web选项而不是本地选项,因为我们使用了Firebase JS SDK。

Web Project

输入应用程序的详细信息,并确保Firebase托管被禁用。一旦完成,点击注册应用程序

Add Firebase Web App

你会在屏幕上收到设置配置的说明,包括你的应用密钥。这些密钥不是你的私人密钥,但它们是访问后端服务所必需的,因为它们允许你将你的代码与Firebase云整合起来。

在代码中留下它们并不是一个好主意,但现在我们不会关注这个。复制给定的代码并保存。当我们设置环境变量时,我们会再来讨论这个话题。

Firebase SDK

现在应用程序已经准备好了,我们可以开始添加Firebase服务,比如说认证。

Authentication

你可以从左边的Build菜单或主屏幕上的产品部分访问认证服务。在那里,点击 "开始",选择你喜欢的登录方式。

我们今天的演示将使用电子邮件/密码提供者,但可以自由地探索更多的选项。Firebase在这里提供了大量的选择。

Authentication Options

一旦你选择了一个提供者,只需启用它,如果需要的话,按照说明进行设置并保存你的改动。

Sign-in Providers

在你的Expo应用程序中配置Firebase SDK

现在是设置Firebase SDK的时候了。为此,我们将把上面提供的Firebase设置代码添加到我们的应用程序中。让我们在应用程序的源文件中创建一个名为config 的文件夹,并添加一个名为firebase.ts 的文件。

在那里,我们要粘贴配置代码。

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyBSKowWPBCynHTYSFBBnJUqP2pI-pr2GJI",
  authDomain: "expo-with-firebase-auth-demo.firebaseapp.com",
  projectId: "expo-with-firebase-auth-demo",
  storageBucket: "expo-with-firebase-auth-demo.appspot.com",
  messagingSenderId: "719336217671",
  appId: "1:719336217671:web:efd7e14078045dc9a72562"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

export default app;

然而,如果我们让它保持原样,我们的钥匙和敏感设置可能会被暴露。相反,我们将使用环境变量将这些信息从我们的提交中排除。

使用环境变量保护Firebase设置

首先,将配置移到位于项目根部的.env 文件中。

FIREBASE_API_KEY=AIzaSyBSKowWPBCynHTYSFBBnJUqP2pI-pr2GJI
FIREBASE_AUTH_DOMAIN=expo-with-firebase-auth-demo.firebaseapp.com
FIREBASE_PROJECT_ID=expo-with-firebase-auth-demo
FIREBASE_STORAGE_BUCKETt=expo-with-firebase-auth-demo.appspot.com
FIREBASE_MESSAGING_SENDER_ID=719336217671
FIREBASE_APP_ID=1:719336217671:web:efd7e14078045dc9a72562

当然,你应该从你自己的项目中提供这些值,而不是使用我的。

不要忘记把这个文件添加到你的.gitignore 。否则,它可能会被提交并意外推送。

与我们可以用CRA做的不同,在访问这些值之前,需要在Expo中进行一些额外的配置。

  1. 把你的应用程序设置从app.json 改为app.config.js 。这样一来,我们就可以访问变量process
  2. 导出文件内的JSON对象,在文件的开头添加export default
  3. 在文件的开头添加import 'dotenv/config'。
  4. 添加一个额外的部分来映射我们的环境变量和代码中可访问的常量。新的部分如下。
"extra": {
      firebaseApiKey: process.env.FIREBASE_API_KEY,
      firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
      firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
      firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
      firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
      firebaseAppId: process.env.FIREBASE_APP_ID
    }

最后,你的文件应该看起来像这样。

import 'dotenv/config';

export default {
  "expo": {
    "name": "expo-with-firebase-auth-demo",
    "slug": "expo-with-firebase-auth-demo",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "extra": {
      firebaseApiKey: process.env.FIREBASE_API_KEY,
      firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
      firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
      firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
      firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
      firebaseAppId: process.env.FIREBASE_APP_ID
    }
  }
}

现在,由于已经安装的软件包expo-constants ,对象的extra 部分中的所有键都可以在整个应用程序中访问。

最后一步是重新编写firebase.ts 配置文件,以使用新的常量而不是硬编码这些键。这个改变很简单,就是用你的配置文件中定义的名称来改变常量的值。

新的 firebase.ts 文件应该看起来像这样。

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import Constants from 'expo-constants';

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: Constants.manifest?.extra?.firebaseApiKey,
  authDomain: Constants.manifest?.extra?.firebaseAuthDomain,
  projectId: Constants.manifest?.extra?.firebaseProjectId,
  storageBucket: Constants.manifest?.extra?.firebaseStorageBucket,
  messagingSenderId: Constants.manifest?.extra?.firebaseMessagingSenderId,
  appId: Constants.manifest?.extra?.firebaseAppId,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

export default app;

添加导航

现在是时候布局我们的应用导航和用户流了,考虑到应用中的两种不同状态:登录和注销的情况。

下面是我们的导航将如何工作。

  • 登出的用户
    • 欢迎屏幕
      • 登录界面
      • 注册屏幕
  • 已登录的用户
    • 首页

让我们把重点放在帮助器和导航的编程上。我们将首先创建屏幕占位符,以使一切都在正确的位置。

让我们创建一个名为screens 的新文件夹,为我们的每个屏幕创建一个文件。现在,所有的屏幕都将有相同的设计。我们将在以后解决这个问题。

你的文件夹结构看起来像这样。

Folder Structure Expo

每个文件将包含以下代码。

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text>Home screen!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

只要确保适当地重命名这些组件。

创建一个认证钩子

首先,我们必须识别一个用户是否被认证,所以我们将建立一个自定义的钩子,它将执行该信息,以及用户信息,如果有一个用户登录的话。

创建一个名为utils 的文件夹,并在里面建立一个新的文件夹hooks ,在这个文件夹下我们将放置一个新的文件,useAuthentication.ts

该文件的代码是这样的。

import React from 'react';
import { getAuth, onAuthStateChanged, User } from 'firebase/auth';

const auth = getAuth();

export function useAuthentication() {
  const [user, setUser] = React.useState<User>();

  React.useEffect(() => {
    const unsubscribeFromAuthStatuChanged = onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        setUser(user);
      } else {
        // User is signed out
        setUser(undefined);
      }
    });

    return unsubscribeFromAuthStatuChanged;
  }, []);

  return {
    user
  };
}

通过调用onAuthStateChanged ,我们订阅了一个事件,该事件在每次授权状态发生变化时都会触发,例如当用户登录或退出应用程序时。

我们使用这个事件来捕获用户信息,并将其正确地设置为钩子状态,然后将其提供给可能需要它的组件。

创建导航路由器

一旦认证工具做好了,我们就可以为认证和未认证的用户建立导航栈。

我们将把所有的导航逻辑分成三个文件。

  • 一个index ,它将处理所有状态下的应用范围的导航。
  • 一个authStack ,包括所有未认证用户的堆栈
  • 一个用于登录用户的userStack

让我们从authStack.tsx. 开始 在项目根目录上的navigation 文件夹下创建这个新文件,该目录也必须创建该文件夹。在那里,放置以下代码。

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import WelcomeScreen from '../screens/Welcome';
import SignInScreen from '../screens/SignInScreen';
import SignOutScreen from '../screens/SignUpScreen';

const Stack = createStackNavigator();

export default function AuthStack() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Welcome" component={WelcomeScreen} />
        <Stack.Screen name="Sign In" component={SignInScreen} />
        <Stack.Screen name="Sign Up" component={SignOutScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

接下来,让我们研究一下userStack.tsx 文件。

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import HomeScreen from '../screens/Home';

const Stack = createStackNavigator();

export default function UserStack() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

最后,我们用 index.tsx 文件将二者连接起来。

import React from 'react';
import { useAuthentication } from '../utils/hooks/useAuthentication';
import UserStack from './userStack';
import AuthStack from './authStack';

export default function RootNavigation() {
  const { user } = useAuthentication();

  return user ? <UserStack /> : <AuthStack />;
}

这个最新的组件使用useAuthentication 钩子来确定我们是否有一个登录的用户,在此基础上,它加载两个应用程序堆栈中的一个。

最后,我们需要将我们的主App.tsx 与导航连接起来,尽管我们还没有屏幕,但我们应该看到加载的是默认的Welcome 屏幕,因为我们没有登录。

import React from 'react';
import './config/firebase';
import RootNavigation from './navigation';

export default function App() {
  return (
    <RootNavigation />
  );
}

到此为止,我们可以通过运行应用程序来确保一切工作正常。

expo start

在你的设备、模拟器或网络上运行该项目后,你应该看到像这样的东西。

Welcome Screen

这不是很令人印象深刻,但我们接下来会在屏幕上做文章。

构建屏幕

构建屏幕无疑是一个应用程序中最重要的方面之一。在本教程中,我们将构建基础部分,而不太关注整体设计。

我们需要设置我们的UI库,我们已经在教程开始时安装了这个库,但还有一些缺失的配置步骤需要说明。

设置react-native-elements

我们不会在这里创建一个自定义主题,但我们确实需要导入库的默认主题。回到App.tsx ,按照官方文档中的详细说明添加主题提供者。

现在,只需将App.tsx 中的所有组件包裹到主题提供者中,如下所示。

import React from 'react';
import { ThemeProvider } from 'react-native-elements';
import './config/firebase';
import RootNavigation from './navigation';

export default function App() {
  return (
    <ThemeProvider>
      <RootNavigation />
    </ThemeProvider>
  );
}

建立欢迎屏幕

我们的欢迎屏幕看起来很好,但它没有功能。我们需要使用React Native元素中的Button组件,为用户添加一种跳转到登录界面或注册界面的方式。

我们需要添加按钮,这些按钮将导航到堆栈中的不同屏幕。

下面是其中一个按钮的样子的例子。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { StackScreenProps } from '@react-navigation/stack';
import { Button } from 'react-native-elements';

const WelcomeScreen: React.FC<StackScreenProps<any>> = ({ navigation }) => {
  return (
    <View style={styles.container}>
      <Text>Welcome screen!</Text>

      <View style={styles.buttons}>
        <Button title="Sign in" buttonStyle={styles.button} onPress={() => navigation.navigate('Sign In')} />
        <Button title="Sign up" type="outline" buttonStyle={styles.button} onPress={() => navigation.navigate('Sign Up')} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 20,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },

  buttons: {
    flex: 1,
  },

  button: {
    marginTop: 10
  }
});

export default WelcomeScreen;


这就是结果。

Sign-in Demonstration

构建注册屏幕

注册屏幕有点复杂和有趣,因为我们需要整合逻辑来在Firebase上创建一个用户。

我们将开始设计这个屏幕,然后我们将添加逻辑来使它像魔术一样工作。这个屏幕包含两个输入元素,一个是电子邮件,一个是密码。它还有一个注册按钮,并可以在出错时显示一个错误信息。

这里是屏幕设计。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { Input, Button } from 'react-native-elements';
import { StackScreenProps } from '@react-navigation/stack';

const SignUpScreen: React.FC<StackScreenProps<any>> = ({ navigation }) => {
  const [value, setValue] = React.useState({
    email: '',
    password: '',
    error: ''
  })

  function signUp() {
    if (value.email === '' || value.password === '') {
      setValue({
        ...value,
        error: 'Email and password are mandatory.'
      })
      return;
    }

    setValue({
      ...value,
      error: ''
    })
  }

  return (
    <View style={styles.container}>
      <Text>Signup screen!</Text>

      {!!value.error && <View style={styles.error}><Text>{value.error}</Text></View>}

      <View style={styles.controls}>
        <Input
          placeholder='Email'
          containerStyle={styles.control}
          value={value.email}
          onChangeText={(text) => setValue({ ...value, email: text })}
          leftIcon={<Icon
            name='envelope'
            size={16}
          />}
        />

        <Input
          placeholder='Password'
          containerStyle={styles.control}
          value={value.password}
          onChangeText={(text) => setValue({ ...value, password: text })}
          secureTextEntry={true}
          leftIcon={<Icon
            name='key'
            size={16}
          />}
        />

        <Button title="Sign up" buttonStyle={styles.control} onPress={signUp} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 20,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },

  controls: {
    flex: 1,
  },

  control: {
    marginTop: 10
  },

  error: {
    marginTop: 10,
    padding: 10,
    color: '#fff',
    backgroundColor: '#D54826FF',
  }
});

export default SignUpScreen;

接下来,让我们从代码中开始使用Firebase的认证服务。

我们需要更新我们的firebase.ts 配置文件,因为我们从未指定要使用认证。我们所要做的就是在文件的开头导入 firebase/auth ,就在导入Constants 。该文件的导入部分看起来像这样。

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import 'firebase/auth';
import Constants from 'expo-constants';

接下来,让我们更新一下SignUpScreen 的代码,以利用这个库。

让我们从导入和本地对象开始。每次我们想访问Firebase服务时,我们需要导入它的配置和任何我们想使用的模数。在我们的例子中。

import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';

const auth = getAuth();

接下来,我们将利用函数createUserWithEmailAndPassword ,在注册过程中在Firebase上创建一个用户。如果成功,我们将把用户送到登录界面。如果不成功,我们将显示一个错误信息和细节。

更新signUp 函数,如下所示。

async function signUp() {
  if (value.email === '' || value.password === '') {
    setValue({
      ...value,
      error: 'Email and password are mandatory.'
    })
    return;
  }

  try {
    await createUserWithEmailAndPassword(auth, value.email, value.password);
    navigation.navigate('Sign In');
  } catch (error) {
    setValue({
      ...value,
      error: error.message,
    })
  }
}

建立签到屏幕

签到屏幕看起来与我们的注册屏幕基本相同。代码有95%是相同的,不同的是我们没有调用createUserWithEmailAndPassword, ,而是调用signInWithEmailAndPassword

下面是代码。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { Input, Button } from 'react-native-elements';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const auth = getAuth();

const SignInScreen = () => {
  const [value, setValue] = React.useState({
    email: '',
    password: '',
    error: ''
  })

  async function signIn() {
    if (value.email === '' || value.password === '') {
      setValue({
        ...value,
        error: 'Email and password are mandatory.'
      })
      return;
    }

    try {
      await signInWithEmailAndPassword(auth, value.email, value.password);
    } catch (error) {
      setValue({
        ...value,
        error: error.message,
      })
    }
  }

  return (
    <View style={styles.container}>
      <Text>Signin screen!</Text>

      {!!value.error && <View style={styles.error}><Text>{value.error}</Text></View>}

      <View style={styles.controls}>
        <Input
          placeholder='Email'
          containerStyle={styles.control}
          value={value.email}
          onChangeText={(text) => setValue({ ...value, email: text })}
          leftIcon={<Icon
            name='envelope'
            size={16}
          />}
        />

        <Input
          placeholder='Password'
          containerStyle={styles.control}
          value={value.password}
          onChangeText={(text) => setValue({ ...value, password: text })}
          secureTextEntry={true}
          leftIcon={<Icon
            name='key'
            size={16}
          />}
        />

        <Button title="Sign in" buttonStyle={styles.control} onPress={signIn} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 20,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },

  controls: {
    flex: 1,
  },

  control: {
    marginTop: 10
  },

  error: {
    marginTop: 10,
    padding: 10,
    color: '#fff',
    backgroundColor: '#D54826FF',
  }
});

export default SignInScreen;

建立主屏幕

让我们保持简单,只提供一个注销的方法。

与注册屏幕类似,首先我们将构建屏幕,然后是退出功能。

下面是HomeScreen 组件设计的代码。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useAuthentication } from '../utils/hooks/useAuthentication';
import { Button } from 'react-native-elements';

export default function HomeScreen() {
  const { user } = useAuthentication();

  return (
    <View style={styles.container}>
      <Text>Welcome {user?.email}!</Text>

      <Button title="Sign Out" style={styles.button} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  button: {
    marginTop: 10
  }
});

现在,让我们通过替换下面的按钮代码来编写signOut 的功能。

<Button title="Sign Out" style={styles.button} onPress={() => signOut(auth)} />

就这样了!

Sign-up Demonstration

结语

将Firebase认证整合到Expo应用中的代码是相当简单的,因为大部分的工作都是在设计屏幕上。有了Firebase,就很容易开始建立安全的应用程序。

今天,我们只是建立了一个最基本的原型,但Firebase提供了更多的认证选项和服务,所以要进一步探索它来建立伟大的产品--它是免费的,可以开始使用谢谢你的阅读!

The postIntegrating Firebase authentication into an Expo mobile appappeared first onLogRocket Blog.