├── .eslintrc.json ├── public ├── favicon.ico └── vercel.svg ├── README.md ├── next.config.js ├── pages ├── dashboard.tsx ├── index.tsx ├── api │ └── hello.ts ├── _app.tsx ├── signup.tsx └── login.tsx ├── next-env.d.ts ├── styles ├── globals.css └── Home.module.css ├── components ├── ProtectedRoute.tsx └── Navbar.tsx ├── config └── firebase.ts ├── tsconfig.json ├── .gitignore ├── package.json └── context └── AuthContext.tsx /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sairajchouhan/nextjs-firebase-auth/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authentication in Next.js with Firebase 2 | 3 | [click here for a video tutorial](https://www.youtube.com/watch?v=ZmpO65DhRN0) 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /pages/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Dashboard = () => { 4 | return
This route is protected
5 | } 6 | 7 | export default Dashboard 8 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import Image from 'next/image' 4 | import styles from '../styles/Home.module.css' 5 | 6 | const Home: NextPage = () => { 7 | return ( 8 |
9 |

Next Firebase Auth

10 |
11 | ) 12 | } 13 | 14 | export default Home 15 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /components/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, { useEffect } from 'react' 3 | import { useAuth } from '../context/AuthContext' 4 | 5 | const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { 6 | const { user } = useAuth() 7 | const router = useRouter() 8 | 9 | useEffect(() => { 10 | if (!user) { 11 | router.push('/login') 12 | } 13 | }, [router, user]) 14 | 15 | return <>{user ? children : null} 16 | } 17 | 18 | export default ProtectedRoute 19 | -------------------------------------------------------------------------------- /config/firebase.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app' 2 | import { getAuth } from 'firebase/auth' 3 | 4 | const firebaseConfig = { 5 | apiKey: 'AIzaSyCE8an26rfTjkg8EFUjeMpUICDDx99Lj7s', 6 | authDomain: 'nextjs-firebase-auth-a4852.firebaseapp.com', 7 | projectId: 'nextjs-firebase-auth-a4852', 8 | storageBucket: 'nextjs-firebase-auth-a4852.appspot.com', 9 | messagingSenderId: '924467240098', 10 | appId: '1:924467240098:web:eb6bd8b51ad23d35278bfb', 11 | } 12 | 13 | const app = initializeApp(firebaseConfig) 14 | export const auth = getAuth() 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-firebase-auth", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "bootstrap": "^5.1.3", 13 | "firebase": "^9.6.7", 14 | "next": "12.1.0", 15 | "react": "17.0.2", 16 | "react-bootstrap": "^2.1.2", 17 | "react-dom": "17.0.2" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "17.0.21", 21 | "@types/react": "17.0.39", 22 | "eslint": "8.10.0", 23 | "eslint-config-next": "12.1.0", 24 | "typescript": "4.5.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | import 'bootstrap/dist/css/bootstrap.min.css' 4 | import Navbar from '../components/Navbar' 5 | import { AuthContextProvider } from '../context/AuthContext' 6 | import { useRouter } from 'next/router' 7 | import ProtectedRoute from '../components/ProtectedRoute' 8 | 9 | const noAuthRequired = ['/', '/login', '/signup'] 10 | 11 | function MyApp({ Component, pageProps }: AppProps) { 12 | const router = useRouter() 13 | 14 | return ( 15 | 16 | 17 | {noAuthRequired.includes(router.pathname) ? ( 18 | 19 | ) : ( 20 | 21 | 22 | 23 | )} 24 | 25 | ) 26 | } 27 | 28 | export default MyApp 29 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container, Nav, Navbar } from 'react-bootstrap' 3 | import Link from 'next/link' 4 | import { useAuth } from '../context/AuthContext' 5 | import { useRouter } from 'next/router' 6 | 7 | const NavbarComp = () => { 8 | const { user, logout } = useAuth() 9 | const router = useRouter() 10 | 11 | return ( 12 | 13 | 14 | 15 | NextJS Firebase Auth 16 | 17 | 18 | 19 | 42 | 43 | 44 | 45 | ) 46 | } 47 | 48 | export default NavbarComp 49 | -------------------------------------------------------------------------------- /context/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from 'react' 2 | import { 3 | onAuthStateChanged, 4 | createUserWithEmailAndPassword, 5 | signInWithEmailAndPassword, 6 | signOut, 7 | } from 'firebase/auth' 8 | import { auth } from '../config/firebase' 9 | 10 | const AuthContext = createContext({}) 11 | 12 | export const useAuth = () => useContext(AuthContext) 13 | 14 | export const AuthContextProvider = ({ 15 | children, 16 | }: { 17 | children: React.ReactNode 18 | }) => { 19 | const [user, setUser] = useState(null) 20 | const [loading, setLoading] = useState(true) 21 | console.log(user) 22 | 23 | useEffect(() => { 24 | const unsubscribe = onAuthStateChanged(auth, (user) => { 25 | if (user) { 26 | setUser({ 27 | uid: user.uid, 28 | email: user.email, 29 | displayName: user.displayName, 30 | }) 31 | } else { 32 | setUser(null) 33 | } 34 | setLoading(false) 35 | }) 36 | 37 | return () => unsubscribe() 38 | }, []) 39 | 40 | const signup = (email: string, password: string) => { 41 | return createUserWithEmailAndPassword(auth, email, password) 42 | } 43 | 44 | const login = (email: string, password: string) => { 45 | return signInWithEmailAndPassword(auth, email, password) 46 | } 47 | 48 | const logout = async () => { 49 | setUser(null) 50 | await signOut(auth) 51 | } 52 | 53 | return ( 54 | 55 | {loading ? null : children} 56 | 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /pages/signup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Button, Form } from 'react-bootstrap' 3 | import { useAuth } from '../context/AuthContext' 4 | 5 | const Signup = () => { 6 | const { user, signup } = useAuth() 7 | console.log(user) 8 | const [data, setData] = useState({ 9 | email: '', 10 | password: '', 11 | }) 12 | 13 | const handleSignup = async (e: any) => { 14 | e.preventDefault() 15 | 16 | try { 17 | await signup(data.email, data.password) 18 | } catch (err) { 19 | console.log(err) 20 | } 21 | 22 | console.log(data) 23 | } 24 | 25 | return ( 26 |
32 |

Signup

33 |
34 | 35 | Email address 36 | 41 | setData({ 42 | ...data, 43 | email: e.target.value, 44 | }) 45 | } 46 | value={data.email} 47 | /> 48 | 49 | 50 | 51 | Password 52 | 57 | setData({ 58 | ...data, 59 | password: e.target.value, 60 | }) 61 | } 62 | value={data.password} 63 | /> 64 | 65 | 66 | 69 |
70 |
71 | ) 72 | } 73 | 74 | export default Signup 75 | -------------------------------------------------------------------------------- /pages/login.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, { useState } from 'react' 3 | import { Button, Form } from 'react-bootstrap' 4 | import { useAuth } from '../context/AuthContext' 5 | 6 | const Login = () => { 7 | const router = useRouter() 8 | const { user, login } = useAuth() 9 | const [data, setData] = useState({ 10 | email: '', 11 | password: '', 12 | }) 13 | 14 | const handleLogin = async (e: any) => { 15 | e.preventDefault() 16 | 17 | console.log(user) 18 | try { 19 | await login(data.email, data.password) 20 | router.push('/dashboard') 21 | } catch (err) { 22 | console.log(err) 23 | } 24 | } 25 | 26 | return ( 27 |
33 |

Login

34 |
35 | 36 | Email address 37 | 39 | setData({ 40 | ...data, 41 | email: e.target.value, 42 | }) 43 | } 44 | value={data.email} 45 | required 46 | type="email" 47 | placeholder="Enter email" 48 | /> 49 | 50 | 51 | 52 | Password 53 | 55 | setData({ 56 | ...data, 57 | password: e.target.value, 58 | }) 59 | } 60 | value={data.password} 61 | required 62 | type="password" 63 | placeholder="Password" 64 | /> 65 | 66 | 69 |
70 |
71 | ) 72 | } 73 | 74 | export default Login 75 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | --------------------------------------------------------------------------------