在Next.js中用Firebase实现认证

673 阅读7分钟

在今天的网络应用中,认证是至关重要的。这是许多开发者在过去不得不实现的一个功能。值得庆幸的是,许多库通过提供许多内置功能使这项工作变得更容易。特别是,Firebase是一个处理用户管理和认证的优秀工具。

在本教程中,我们将介绍如何使用Firebase实现认证。

什么是Firebase?

Firebase在2014年被谷歌收购,是一个提供一整套产品的平台,包括但不限于。

  • 实时数据库
  • 身份认证
  • 云信息传递

这些产品允许开发者轻松快速地创建和运行应用程序。

创建一个Firebase账户

在你写一行代码之前,你需要一个Firebase账户。到这里来创建一个。

在Firebase中,如果你想要API密钥,你就需要创建应用程序。这些应用必须属于项目。因此,如果你还没有建立一个项目,你需要创建一个。一旦你这样做了,就创建一个应用程序来获得你的密钥。

Creating a new app for our project in Firebase

iOS、安卓和网络是一些可用的格式。

现在,点击项目概览旁边的设置图标(在你屏幕的左上方)。在项目设置常规下,你应该看到你的应用程序和它的配置。

在你进入代码之前,你需要启用你想使用的登录方法。要做到这一点,请点击认证,然后点击签到方法。每种方法都有不同的配置,但为了本教程的目的,我将专注于传统的电子邮件/密码方法。

Email/password sign in method enabled

启用你的签到供应商。

将本地环境添加到Next.js中

现在你有了你的钥匙,是时候把它们添加到你的Next.js项目中了。

提示:如果你没有一个已经创建的环境,不要担心。这个命令会让你开始行动。

npx create-next-app
# or
yarn create next-app

Next.js项目会自动忽略.env.local ,这要归功于它的.gitignore 文件,所以你可以复制/粘贴你的密钥,而不用担心它们会被意外地提交到GitHub。

NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=<YOUR_API_KEY>
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<YOUR_DOMAIN>
NEXT_PUBLIC_FIREBASE_PROJECT_ID=<YOUR_PROJECT_ID>

别忘了:在Next.js中,命名环境变量的惯例是,它们必须以NEXT_PUBLIC开头。

安装Firebase

继续安装Firebase库。

npm install --save Firebase
# or
yarn add Firebase

在Next.js中创建一个Firebase实例

太好了!库已经安装好了,你的API密钥也已经到手。库已经安装好了,你的API密钥也设置好了。是时候使用这些密钥来创建一个Firebase实例了。当然,Firebase有很多有用的工具,但为了这篇文章,我们将只专注于认证。因此,你只需要Firebase/authapiKey,authDomain, 和projectId 的证书。

import Firebase from 'Firebase/app';
import 'Firebase/auth';

const FirebaseCredentials = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID
}
// if a Firebase instance doesn't exist, create one
if (!Firebase.apps.length) {
  Firebase.initializeApp(FirebaseCredentials)
}

export default Firebase;

倾听Firebase的变化

你首先需要的是一个authUser ,你可以在你的应用程序中访问它。这个变量不仅有助于用户管理,也有助于相应地重定向路线。

例如,如果authUser 是空的,意味着用户还没有登录,当这个人试图访问一个受保护的路由(例如,一个仪表板)时,你应该把他们重定向到登录页面。

值得庆幸的是,Firebase.auth 可以跟踪状态,并且有一个内置的函数叫onAuthStateChanged ,允许你监听状态变化。

当状态改变时,根据你的需要对用户进行格式化,最后,将其设置为你的authUser 变量。使用loading 变量来表示Firebase是否在获取数据。

import { useState, useEffect } from 'react'
import Firebase from './Firebase';

const formatAuthUser = (user) => ({
  uid: user.uid,
  email: user.email
});

export default function useFirebaseAuth() {
  const [authUser, setAuthUser] = useState(null);
  const [loading, setLoading] = useState(true);

  const authStateChanged = async (authState) => {
    if (!authState) {
      setAuthUser(null)
      setLoading(false)
      return;
    }

    setLoading(true)
    var formattedUser = formatAuthUser(authState);
    setAuthUser(formattedUser);    
    setLoading(false);
  };

// listen for Firebase state change
  useEffect(() => {
    const unsubscribe = Firebase.auth().onAuthStateChanged(authStateChanged);
    return () => unsubscribe();
  }, []);

  return {
    authUser,
    loading
  };
}

创建一个用户上下文

为了在你的应用程序中访问authUser 和加载变量,你将使用Context API。

提示:不熟悉React Context?不要犹豫,看看官方的文档吧。

首先,用createContext 创建你的上下文对象,并设置默认值(authUser 作为null ,加载作为true )。然后,从useFirebaseAuth 获取实际的authUserloading 变量,并将其传递给提供者组件。

你还应该添加一个自定义钩子,在这种情况下,useAuth ,以访问当前的上下文值。

import { createContext, useContext, Context } from 'react'
import useFirebaseAuth from '../lib/useFirebaseAuth';

const authUserContext = createContext({
  authUser: null,
  loading: true
});

export function AuthUserProvider({ children }) {
  const auth = useFirebaseAuth();
  return <authUserContext.Provider value={auth}>{children}</authUserContext.Provider>;
}
// custom hook to use the authUserContext and access authUser and loading
export const useAuth = () => useContext(authUserContext);

然后,在我们的_app.js ,将这个提供者包裹在你的应用程序中。这可以确保子组件能够访问你的用户上下文。

import { AuthUserProvider } from '../context/AuthUserContext';

function MyApp({ Component, pageProps }) {
  return <AuthUserProvider><Component {...pageProps} /></AuthUserProvider>
}

export default MyApp

创建受保护的路由

受保护的路由是你的应用程序的页面或部分,应该只被某些用户访问。在这种情况下,只有登录的用户才能访问这些内容。要设置这个,从你的自定义useAuth() 钩子中获取authUserloading

有了这些变量,检查Firebase是否还在获取数据(即加载的是true ),如果不是,authUser 是否是null 。如果是这样的话,那么用户就没有登录,你应该把他们重定向到登录页面。

在你的应用程序中进行测试,确保重定向是正确发生的。

import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../context/AuthUserContext';

import {Container, Row, Col} from 'reactstrap';

const LoggedIn = () => {
  const { authUser, loading } = useAuth();
  const router = useRouter();

  // Listen for changes on loading and authUser, redirect if needed
  useEffect(() => {
    if (!loading && !authUser)
      router.push('/')
  }, [authUser, loading])

  return (
    //Your logged in page
  )
}

export default LoggedIn;

在Next.js中添加登录、注册和注销的功能

现在,让我们来看看多汁的部分。Firebase的一个伟大之处在于它有许多内置的功能,用于登录、创建用户和注销。

所以,让我们把它们添加到useFirebaseAuth函数中。使用Firebase.auth() 来访问不同的函数(signInWithEmailAndPassword,createUserWithEmailAndPassword, 和signOut )。

export default function useFirebaseAuth() {
  // ...
  const clear = () => {
    setAuthUser(null);
    setLoading(true);
  };

  const signInWithEmailAndPassword = (email, password) =>
    Firebase.auth().signInWithEmailAndPassword(email, password);

  const createUserWithEmailAndPassword = (email, password) =>
    Firebase.auth().createUserWithEmailAndPassword(email, password);

  const signOut = () =>
    Firebase.auth().signOut().then(clear);

  useEffect(() => {
    const unsubscribe = Firebase.auth().onAuthStateChanged(authStateChanged);
    return () => unsubscribe();
  }, []);

  return {
    authUser,
    loading,
    signInWithEmailAndPassword,
    createUserWithEmailAndPassword,
    signOut
  };
}

不要忘记更新你的上下文文件中的默认值。

const authUserContext = createContext({
  authUser: null,
  loading: true,
  signInWithEmailAndPassword: async () => {},
  createUserWithEmailAndPassword: async () => {},
  signOut: async () => {}
});

export function AuthUserProvider({ children }) {
  const auth = useFirebaseAuth();
  return <authUserContext.Provider value={auth}>{children}</authUserContext.Provider>;
}

创建注册页面

在你的注册页面中,使用你的useAuth hook ,再次检索你的函数来创建一个用户。createUserWithEmailAndPassword ,需要两个参数:电子邮件和密码。

完成表单验证后,调用这个函数。如果它成功返回一个authUser ,那么你可以相应地重定向用户。

import { useState } from 'react';
import { useRouter } from 'next/router';

import { useAuth } from '../context/AuthUserContext';

import {Container, Row, Col, Button, Form, FormGroup, Label, Input, Alert} from 'reactstrap';

const SignUp = () => {
  const [email, setEmail] = useState("");
  const [passwordOne, setPasswordOne] = useState("");
  const [passwordTwo, setPasswordTwo] = useState("");
  const router = useRouter();
  const [error, setError] = useState(null);

  const { createUserWithEmailAndPassword } = useAuth();

  const onSubmit = event => {
    setError(null)
    //check if passwords match. If they do, create user in Firebase
    // and redirect to your logged in page.
    if(passwordOne === passwordTwo)
      createUserWithEmailAndPassword(email, passwordOne)
      .then(authUser => {
        console.log("Success. The user is created in Firebase")
        router.push("/logged_in");
      })
      .catch(error => {
        // An error occurred. Set error message to be displayed to user
        setError(error.message)
      });
    else
      setError("Password do not match")
    event.preventDefault();
  };

  return (
    <Container className="text-center custom-container">
      <Row>
        <Col>
          <Form 
            className="custom-form"
            onSubmit={onSubmit}>
          { error && <Alert color="danger">{error}</Alert>}
            <FormGroup row>
              <Label for="signUpEmail" sm={4}>Email</Label>
              <Col sm={8}>
                <Input
                  type="email"
                  value={email}
                  onChange={(event) => setEmail(event.target.value)}
                  name="email"
                  id="signUpEmail"
                  placeholder="Email" />
              </Col>
            </FormGroup>
            <FormGroup row>
              <Label for="signUpPassword" sm={4}>Password</Label>
              <Col sm={8}>
                <Input
                  type="password"
                  name="passwordOne"
                  value={passwordOne}
                  onChange={(event) => setPasswordOne(event.target.value)}
                  id="signUpPassword"
                  placeholder="Password" />
              </Col>
            </FormGroup>
            <FormGroup row>
              <Label for="signUpPassword2" sm={4}>Confirm Password</Label>
              <Col sm={8}>
                <Input
                  type="password"
                  name="password"
                  value={passwordTwo}
                  onChange={(event) => setPasswordTwo(event.target.value)}
                  id="signUpPassword2"
                  placeholder="Password" />
              </Col>
            </FormGroup>
            <FormGroup row>
             <Col>
               <Button>Sign Up</Button>
             </Col>
           </FormGroup>
          </Form>
        </Col>
      </Row>
    </Container>
  )
}

export default SignUp;

添加一个退出按钮

签出也是非常直接的。signOut()useAuth() ,并将其添加到一个按钮或一个链接中。

import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../context/AuthUserContext';

import {Container, Row, Col, Button} from 'reactstrap';

const LoggedIn = () => {
  const { authUser, loading, signOut } = useAuth();
  const router = useRouter();

  // Listen for changes on loading and authUser, redirect if needed
  useEffect(() => {
    if (!loading && !authUser)
      router.push('/')
  }, [authUser, loading])

  return (
    <Container>
      // ...
      <Button onClick={signOut}>Sign out</Button>
      // ...
    </Container>
  )
}

export default LoggedIn;

创建一个登录页面

最后是登录功能!它与前两个功能完全相同。从useAuth() 中获取signInWithEmailAndPassword() ,并传入用户的电子邮件和密码。如果它们是正确的,就重定向给用户,如果不是,就显示正确的错误信息。

import { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';

import { useAuth } from '../context/AuthUserContext';

import {Container, Row, Col, Button, Form, FormGroup, Label, Input, Alert} from 'reactstrap';

export default function Home() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState(null);
  const router = useRouter();
  const { signInWithEmailAndPassword } = useAuth();

  const onSubmit = event => {
    setError(null)
    signInWithEmailAndPassword(email, password)
    .then(authUser => {
      router.push('/logged_in');
    })
    .catch(error => {
      setError(error.message)
    });
    event.preventDefault();
  };

  return (
    <Container className="text-center" style={{ padding: '40px 0px'}}>
      <Row>
        <Col>
          <h2>Login</h2>
        </Col>
      </Row>
      <Row style={{maxWidth: '400px', margin: 'auto'}}>
        <Col>
          <Form onSubmit={onSubmit}>
          { error && <Alert color="danger">{error}</Alert>}
            <FormGroup row>
              <Label for="loginEmail" sm={4}>Email</Label>
              <Col sm={8}>
                <Input
                  type="email"
                  value={email}
                  onChange={(event) => setEmail(event.target.value)}
                  name="email"
                  id="loginEmail"
                  placeholder="Email" />
              </Col>
            </FormGroup>
            <FormGroup row>
              <Label for="loginPassword" sm={4}>Password</Label>
              <Col sm={8}>
                <Input
                  type="password"
                  name="password"
                  value={password}
                  onChange={(event) => setPassword(event.target.value)}
                  id="loginPassword"
                  placeholder="Password" />
              </Col>
            </FormGroup>
            <FormGroup row>
             <Col>
               <Button>Login</Button>
             </Col>
           </FormGroup>
           <FormGroup row>
            <Col>
              No account? <Link href="/sign_up">Create one</Link>
            </Col>
          </FormGroup>
          </Form>
        </Col>
      </Row>
    </Container>
  )
}

总结

在本教程中,我们介绍了如何创建一个Firebase账户、项目和应用程序。然后,我们学习了如何使用React Context来创建一个用户环境。在这个上下文中,我们添加了用户和加载变量,以及记录、注册和退出等功能。最后,我们使用这些函数在我们的Next.js应用中实现了认证,这要感谢Firebase!

使用Firebase在Next.js中实现身份验证》一文首次出现在LogRocket博客上。