React(Next.js) × Firebase에서 전체 개발 인증 주위
개요
제목이 그대로 유지됨(Next.js)× Firebase 환경에서 인증 주위 전체가 개발됐기 때문에 기사에 남는다.
경고와 같은 구성 요소는 Material UI를 사용합니다.
해본 일
다음 네 개.
전제 조건
firebase.ts
import firebase from "firebase/compat/app"
import "firebase/compat/auth"
import "firebase/compat/firestore"
import "firebase/compat/storage"
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGE_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}
const firebaseApp = firebase.initializeApp(firebaseConfig)
export const auth = firebase.auth()
export default firebase
사용자 등록
일단 자세부터 잡자.야!
Signup.tsx
<form onSubmit={handleSubmit}>
<div
css={css`
display: flex;
justify-content: center;
align-items: center;
`}
>
<InputLabel>メールアドレス</InputLabel>
<TextField
name="email"
type="email"
size="small"
onChange={handleChangeEmail}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 16px;
`}
>
<InputLabel>パスワード</InputLabel>
<TextField
name="password"
type="password"
size="small"
onChange={handleChangePassword}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 16px;
`}
>
<Button type="submit" variant="outlined">
登録
</Button>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 24px;
`}
>
<Link href={"/login"}>
<a>すでに登録している人はこちら</a>
</Link>
</div>
</form>
InputLabel, Text Field, Button은 Material UI, Link는 Next입니다.잘 부탁드립니다.
CSS도 잘 어울려요.
handleSubmit, handleChangePassword는 정의되지 않았습니다.용도는 이름과 같이 각각 설치해 주십시오.
Signup.tsx
import { auth } from "../src/firebase"
const router = useRouter()
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
await auth.createUserWithEmailAndPassword(email, password)
router.push("/")
}
const handleChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.currentTarget.value)
}
const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.currentTarget.value)
}
handleChangeEmail,handleChangePassword는 state의 set만 합니다.문자를 입력할 때마다 해당하는 state에 값을 저장합니다.그리고 저장된 이메일,password를Firebase로 나누어 새 사용자를 등록합니다.
그걸 진행한 건handle Submit 내
auth.createUserWithEmailAndPassword(email, password)
야.이 줄은 사용자를 등록할 수 있어 Firebase가 강하다.
사용자가 등록한 후 루트 페이지로 이동합니다.노선으로 날아갔지만 여긴 당연히 어디든 가능하지.
이상은 새로 등록한 일입니다.
코드 전문 여기 있습니다.
Signup.tsx
Signup.tsx
import React, { useState } from "react"
import { useRouter } from "next/router"
import Link from "next/link"
import { Alert, Button, InputLabel, Snackbar, TextField } from "@mui/material"
import { css } from "@emotion/react"
import { auth } from "../src/firebase"
import { useAuthContext } from "../src/context/AuthContext"
const Signup = () => {
const router = useRouter()
const { user } = useAuthContext()
const isLoggedIn = !!user
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
await auth.createUserWithEmailAndPassword(email, password)
router.push("/")
}
const handleClose = async () => {
await router.push("/")
}
const handleChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.currentTarget.value)
}
const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.currentTarget.value)
}
return (
<div
css={css`
display: flex;
justify-content: space-between;
align-items: center;
flex-flow: column;
`}
>
<Snackbar
open={isLoggedIn}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={3000}
key={"top" + "center"}
onClose={handleClose}
>
<Alert onClose={handleClose} severity="warning">
すでにログインしています
</Alert>
</Snackbar>
<h2>ユーザー登録</h2>
<form onSubmit={handleSubmit}>
<div
css={css`
display: flex;
justify-content: center;
align-items: center;
`}
>
<InputLabel>メールアドレス</InputLabel>
<TextField
name="email"
type="email"
size="small"
onChange={handleChangeEmail}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 16px;
`}
>
<InputLabel>パスワード</InputLabel>
<TextField
name="password"
type="password"
size="small"
onChange={handleChangePassword}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 16px;
`}
>
<Button type="submit" variant="outlined">
登録
</Button>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 24px;
`}
>
<Link href={"/login"}>
<a>すでに登録している人はこちら</a>
</Link>
</div>
</form>
</div>
)
}
export default Signup
아래 액세스 제어에서 한 내용도 살짝 섞였으니 스냅바와 useAuthContext() 등 위에 설명되지 않은 내용은 잠시 무시해 주십시오.또한 완성형 UI는 여기에 있습니다.
아니에요.다음에 갑시다!
로그인
논리는 기본적으로 사용자 로그인과 같다.
실제 로그인 처리는 사용자 로그인과 같은 줄만 있습니다.
특별히 쓸 일이 없기 때문에 코드 전문을 즉시 게재합니다.
Login.tsx
Login.tsx
import React, { useState } from "react"
import { useRouter } from "next/router"
import Link from "next/link"
import { Alert, Button, InputLabel, Snackbar, TextField } from "@mui/material"
import { css } from "@emotion/react"
import { auth } from "../src/firebase"
import { useAuthContext } from "../src/context/AuthContext"
const Login = () => {
const { user } = useAuthContext()
const isLoggedIn = !!user
const router = useRouter()
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
await auth.signInWithEmailAndPassword(email, password)
router.push("/")
}
const handleChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.currentTarget.value)
}
const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.currentTarget.value)
}
const handleClose = async () => {
await router.push("/")
}
return (
<div
css={css`
display: flex;
justify-content: space-between;
align-items: center;
flex-flow: column;
`}
>
<Snackbar
open={isLoggedIn}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={3000}
key={"top" + "center"}
onClose={handleClose}
>
<Alert onClose={handleClose} severity="warning">
すでにログインしています
</Alert>
</Snackbar>
<Snackbar
open={!isLoggedIn}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={3000}
key={"top" + "center"}
>
<Alert severity="warning">ログインしてください</Alert>
</Snackbar>
<h2>ログイン</h2>
<form onSubmit={handleSubmit}>
<div
css={css`
display: flex;
justify-content: center;
align-items: center;
`}
>
<InputLabel>メールアドレス</InputLabel>
<TextField
name="email"
type="email"
size="small"
onChange={handleChangeEmail}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 16px;
`}
>
<InputLabel>パスワード</InputLabel>
<TextField
name="password"
type="password"
size="small"
onChange={handleChangePassword}
css={css`
padding-left: 12px;
`}
/>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 16px;
`}
>
<Button type="submit" variant="outlined">
ログイン
</Button>
</div>
<div
css={css`
display: flex;
justify-content: flex-end;
margin-top: 24px;
`}
>
ユーザ登録は
<Link href={"/signup"}>
<a>こちら</a>
</Link>
から
</div>
</form>
</div>
)
}
export default Login
await auth.signInWithEmailAndPassword(email, password)
의 한 줄에 로그인 처리를 합니다.방법명도 알기 쉽고.이는 사용자 로그인과 마찬가지로 액세스 제어 내용이 뒤섞여 있어 내용을 대충 봐도 문제없다.
우선 완성형 UI는 이쪽에 있습니다.
취소
다음handle Logiput, 로그아웃 버튼(상)에서 훈련합니다!
import { auth } from "../firebase"
import { useRouter } from "next/router"
const router = useRouter()
const handleLogout = async () => {
await auth.signOut()
await router.push("/about")
}
제 경우는 이 기사에 등장하지 않은 햄버거 메뉴의 버튼에 담겨 있습니다.먼저 해야 할 일은 위의 함수를 온클릭에 넣는 것이기 때문에 이곳의 전문 UI는 생략되었다.
그래도 대충 인증 처리 한 줄이면 끝나요. 정말 간단하고 대단하네요.
액세스 제어
로그인하지 않은 경우
로그인하지 않은 경우 특정 페이지에 들어가려면 로그인 페이지로 강제 마이그레이션한 후 "로그인하지 않았기 때문에 안 됩니다~"라고 경고합니다.
특정한 것이라고는 하지만 거의 모든 페이지가 이 처리를 하려고 한다.
따라서 모든 페이지 component 간섭 가능app.tsx에 뭐라도 더 써.
추기 후의app.tsx의 코드 전문은 이런 느낌입니다.
_app.tsx
import { css } from "@emotion/react"
import Header from "../src/components/header"
import type { AppProps } from "next/app"
import { AuthProvider } from "../src/context/AuthContext"
const App = ({ Component, pageProps }: AppProps) => {
return (
<AuthProvider>
<Header />
<Component
{...pageProps}
/>
</AuthProvider>
)
}
export default App
헤더는 단지 꼬리표일 뿐, 햄버거 메뉴에만 나타나 Auth Provider가 어떤 녀석인지 느낀다.AuthProvider는 대체로 현재 로그인 여부를 감시하는 구성 요소입니다.
감시에 의하면 땡땡이를 치지 않으면××이런 처리는 이 점에 연결될 수 있다.
로그인 감시는auth입니다.onAuthStateChanged라는 방법으로 구현할 수 있습니다.
이 방법은 예를 들면 다음과 같다.
auth.onAuthStateChanged((user) => {
if (user) {
// ログイン時の処理
} else {
// 未ログイン時の処理
}
});
로그인한 경우user에 사용자 정보가 있고 로그인하지 않으면null이 있습니다.이 방법을 사용하면 각각 로그인할 때와 미로그인할 때의 처리를 실현할 수 있다.
이거 auth.AuthProvider에서 onAuthStateChanged를 사용합니다.
이번 요구사항은'로그인하지 않을 때 특정 페이지로 들어가 강제로 로그인 페이지로 이동한 후 경보를 보내기'이기 때문에 어떤 페이지에 들어갈 때마다'로그인 여부','특정 페이지에 있는지 확인해야 한다.
이런 건 useEffect로 이루어져요.
따라서 AuthContext는 다음과 같이 구현됩니다.
AuthContext.tsx
AuthContext.tsx
import { ReactNode, createContext, useState, useContext, useEffect } from "react"
import firebase, { auth } from "../firebase"
import { useRouter } from "next/router"
type UserType = firebase.User | null
type AuthContextProps = {
user: UserType
}
type AuthProps = {
children: ReactNode
}
const AuthContext = createContext<Partial<AuthContextProps>>({})
export const useAuthContext = () => {
return useContext(AuthContext)
}
export const AuthProvider = ({ children }: AuthProps) => {
const router = useRouter()
const [user, setUser] = useState<UserType>(null)
const isAvailableForViewing =
router.pathname === "/about" ||
router.pathname === "/login" ||
router.pathname === "/signup"
const value = {
user,
}
useEffect(() => {
const authStateChanged = auth.onAuthStateChanged(async (user) => {
setUser(user)
!user && !isAvailableForViewing && (await router.push("/login"))
})
return () => {
authStateChanged()
}
}, [])
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
!user && !isAvailableForViewing && (await router.push("/login"))
에서 "지금 로그인하지 않았습니다. 열람 가능한 페이지에 들어가면 로그인 페이지로 이동합니다.또한 '현재 사용자가 로그인하는지 여부' 의 값도 다른 구성 요소를 가로질러 사용하기 때문에useContext로 실행됩니다.
이렇게 하면'로그인하지 않았을 때 특정한 페이지로 들어가 강제로 로그인 페이지로 이동한 후 경보를 보낸다'의 절반이 완성된다.
그리고 로그인 페이지에 경보를 보내세요.
Login.tsx
const Login = () => {
const { user } = useAuthContext()
const isLoggedIn = !!user
return (
...(中略)...
<Snackbar
open={!isLoggedIn}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={3000}
key={"top" + "center"}
>
<Alert severity="warning">ログインしてください</Alert>
</Snackbar>
...(中略)...
)
}
게재됐으니 필요한 곳만 뽑아 로긴.tsx를 다시 불러옵니다.useAuthContext().사용자 정보가 들어오는 속성에 로그인하면user가 위에서 로컬 전체에 접근할 수 있습니다.
bool로 변환한 후 가짜라면 로그인하지 않기 때문에 경보를 표시하는 Snakbar.
Material UI의 Alert를 Snapbar에 배치한 이유는 Alert에 표시되는 proops가 없기 때문입니다.나는 공식을 모방하여 썼다.
이렇게 하면 실현할 수 있다.
경고의 UI는 이런 느낌입니다.
로그인 시
로그인 페이지와 새 로그인 페이지에 가면 "이미 로그인했어~"라는 경고가 울려 강제 마이그레이션됩니다.
이쪽 처리도 이미 Logiin입니다.tsx, Signup.tsx 전문에 기재되어 있지만 필요한 부분만 다시 한 번 뜯어보겠습니다.
Signup.tsx
const Login = () => {
const { user } = useAuthContext()
const isLoggedIn = !!user
const handleClose = async () => {
await router.push("/")
}
return (
...(中略)...
<Snackbar
open={isLoggedIn}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={3000}
key={"top" + "center"}
onClose={handleClose}
>
<Alert onClose={handleClose} severity="warning">
すでにログインしています
</Alert>
</Snackbar>
...(中略)...
)
}
Signup.tsx의 일부분을 싣고 있지만, Logiin.tsx에도 같은 일이 적혀 있다.로그인하지 않았을 때의 접근 제어가 닿은 것처럼useAuthContext ().user는 "로그인하면 사용자 정보가 있는 속성"이고 isLoggedIn이 바로 그 책입니다.
따라서 로그인 상태에서 새 로그인 페이지, 로그인 페이지로 이동하면 경고가 먼저 표시됩니다.
그리고 onClose에서handleClose에 불을 붙여 노선의 페이지로 강제로 이동합니다.
이렇게 접근 제어도 완성되었습니다!🙏
먼저 로그인할 때 경고 UI가 먼저 업로드됩니다.
감상
개발자를 위한 다양한 서비스와 결합해 새 앱을 만들 때'Firebase는 Auth만 사용한다'는 점을 간혹 발견할 수 있다.
나는 그 이유를 조금 알 것 같다.Firebase Authentication, 간단하면서도 세게~
참고 문장
Reference
이 문제에 관하여(React(Next.js) × Firebase에서 전체 개발 인증 주위), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/rinda_1994/articles/482a4c5967c3c3텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)