├── .eslintrc.json ├── .husky └── pre-commit ├── public ├── favicon.ico └── vercel.svg ├── components └── layout │ ├── footer.tsx │ ├── index.tsx │ └── header.tsx ├── next.config.js ├── postcss.config.js ├── next-env.d.ts ├── tailwind.config.js ├── .lintstagedrc.js ├── pages ├── index.tsx ├── api │ └── hello.ts ├── _app.tsx ├── private.tsx ├── privatessr.tsx ├── signin.tsx └── signup.tsx ├── .env ├── lib ├── session.ts ├── firebaseConfig │ ├── init.ts │ └── init-admin.ts └── authContext.tsx ├── tsconfig.json ├── .gitignore ├── .github └── workflows │ └── build.yml ├── README.md └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vortexory/nextjs-firebase-tailwindcss/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /components/layout/footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer(props: any) { 2 | return
footer
; 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | }; 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const buildEslintCommand = (filenames) => 4 | `next lint --fix --file ${filenames 5 | .map((f) => path.relative(process.cwd(), f)) 6 | .join(' --file ')}` 7 | 8 | module.exports = { 9 | '*.{js,jsx,ts,tsx}': [buildEslintCommand], 10 | } 11 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | 4 | const Home: NextPage = () => { 5 | return ( 6 | <> 7 | 8 | Home 9 | 10 | 11 |
Sweet home
12 | 13 | ); 14 | }; 15 | 16 | export default Home; 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | process.env.ANALYZE= 2 | 3 | #Firebase-admin 4 | type= 5 | project_id= 6 | private_key_id= 7 | private_key= 8 | client_email= 9 | client_id= 10 | auth_uri= 11 | token_uri= 12 | auth_provider_x509_cert_url= 13 | client_x509_cert_url= 14 | 15 | #firebase 16 | NEXT_PUBLIC_apiKey= 17 | NEXT_PUBLIC_authDomain= 18 | NEXT_PUBLIC_projectId= 19 | NEXT_PUBLIC_appId= 20 | 21 | # 22 | URL=http://localhost:3000 -------------------------------------------------------------------------------- /lib/session.ts: -------------------------------------------------------------------------------- 1 | import nookies from "nookies"; 2 | import { GetServerSidePropsContext, NextApiHandler } from "next"; 3 | import adminInit from "./firebaseConfig/init-admin"; 4 | 5 | export const authServer = async (ctx: GetServerSidePropsContext) => { 6 | const { idToken } = nookies.get(ctx); 7 | 8 | try { 9 | return adminInit.auth().verifyIdToken(idToken); 10 | } catch (err) { 11 | return null; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "tailwindcss/tailwind.css"; 2 | import type { AppProps } from "next/app"; 3 | import Layout from "../components/layout"; 4 | import FirebaseProvider from "../lib/authContext"; 5 | import "../lib/firebaseConfig/init"; 6 | 7 | function MyApp({ Component, pageProps }: AppProps) { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | export default MyApp; 17 | -------------------------------------------------------------------------------- /components/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import Header from "./header"; 2 | import Footer from "./footer"; 3 | 4 | type Props = { 5 | children: React.ReactNode; 6 | }; 7 | 8 | export default function Layout({ children }: Props) { 9 | return ( 10 |
14 |
15 |
16 |
17 |
{children}
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /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 | package-lock.json 8 | yarn.lock 9 | pnpm-lock.yaml 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # local env files 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | # vercel 37 | .vercel 38 | -------------------------------------------------------------------------------- /pages/private.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { NextPage } from "next"; 3 | import Head from "next/head"; 4 | import { useAuth } from "../lib/authContext"; 5 | 6 | const Home: NextPage = () => { 7 | const { user, loading } = useAuth(); 8 | 9 | if (loading) return

Loading...

; 10 | if (!user) return

U need to login

; 11 | 12 | return ( 13 | <> 14 | 15 | {" "} 16 | Private 17 | 18 | 19 |
20 |

Email : {user?.claims.email}

21 | Private 22 |
23 | 24 | ); 25 | }; 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | Build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 16 14 | 15 | - run: yarn install 16 | - name: build 17 | # TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets 18 | # env: 19 | # REDIS_HOST: ${{ secrets.REDIS_HOST }} 20 | # REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }} 21 | run: yarn build 22 | -------------------------------------------------------------------------------- /pages/privatessr.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage, GetServerSideProps } from "next"; 2 | import Head from "next/head"; 3 | import { authServer } from "../lib/session"; 4 | import type { TIdTokenResult } from "../lib/authContext"; 5 | import React, { ReactNode } from "react"; 6 | 7 | export const getServerSideProps: GetServerSideProps = async (ctx) => { 8 | const user = await authServer(ctx); 9 | 10 | return { props: { user } }; 11 | }; 12 | 13 | const Home: NextPage = ({ 14 | user, 15 | }: { 16 | user?: TIdTokenResult; 17 | children?: ReactNode; 18 | }) => { 19 | if (!user) return

U need to login

; 20 | 21 | return ( 22 | <> 23 | 24 | Private SSR 25 | 26 | 27 |
28 |

Email : {user.claims.email}

29 | Private with SSR 30 |
31 | 32 | ); 33 | }; 34 | 35 | export default Home; 36 | -------------------------------------------------------------------------------- /lib/firebaseConfig/init.ts: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp, getApps } from "firebase/app"; 3 | 4 | // TODO: Add SDKs for Firebase products that you want to use 5 | // https://firebase.google.com/docs/web/setup#available-libraries 6 | 7 | // Your web app's Firebase configuration 8 | function initFirebase() { 9 | const firebaseConfig = { 10 | apiKey: process.env.NEXT_PUBLIC_apiKey, 11 | authDomain: process.env.NEXT_PUBLIC_authDomain, 12 | projectId: process.env.NEXT_PUBLIC_projectId, 13 | storageBucket: process.env.NEXT_PUBLIC_storageBucket, 14 | messagingSenderId: process.env.NEXT_PUBLIC_messagingSenderId, 15 | appId: process.env.NEXT_PUBLIC_appId, 16 | }; 17 | 18 | // Initialize Firebase 19 | const apps = getApps(); 20 | if (!apps.length) { 21 | initializeApp(firebaseConfig); 22 | } 23 | } 24 | 25 | initFirebase(); 26 | -------------------------------------------------------------------------------- /lib/firebaseConfig/init-admin.ts: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import admin from "firebase-admin"; 3 | 4 | // TODO: Add SDKs for Firebase products that you want to use 5 | // https://firebase.google.com/docs/web/setup#available-libraries 6 | 7 | const serviceAccount = { 8 | type: process.env.type, 9 | project_id: process.env.project_id, 10 | private_key_id: process.env.private_key_id, 11 | private_key: process.env.private_key?.replace(/\\n/g, "\n"), 12 | client_email: process.env.client_email, 13 | client_id: process.env.client_id, 14 | auth_uri: process.env.auth_uri, 15 | token_uri: process.env.token_uri, 16 | auth_provider_x509_cert_url: process.env.auth_provider_x509_cert_url, 17 | client_x509_cert_url: process.env.client_x509_cert_url, 18 | } as admin.ServiceAccount; 19 | 20 | if (!admin.apps.length) { 21 | admin.initializeApp({ 22 | credential: admin.credential.cert(serviceAccount), 23 | }); 24 | } 25 | 26 | export default admin; 27 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | 🚀 NextJS, FirebaseAuth & Tailwindcss with Typescript 4 | 5 | 📝 Written with TypeScript 6 | 7 | 🔒 Client-side authentication sample with Credentials/Google Signin 8 | 9 | 🔒 Server-side authentication with cookies sample 10 | 11 | 🦚 with Tailwindcss layout 12 | 13 | ## Getting Started 14 | 15 | First, run the development server: 16 | 17 | ```bash 18 | npm run dev 19 | # or 20 | yarn dev 21 | ``` 22 | 23 | ## Firebase Config 24 | 25 | Firebase config: Console > Project settings > General. 26 | 27 | Firebase-Admin config: Console > Project settings > Service accounts. 28 | 29 | Update `.env` 30 | 31 | ```bash 32 | #Firebase-admin 33 | type= 34 | project_id= 35 | private_key_id= 36 | private_key= 37 | client_email= 38 | client_id= 39 | auth_uri= 40 | token_uri= 41 | auth_provider_x509_cert_url= 42 | client_x509_cert_url= 43 | 44 | #firebase 45 | NEXT_PUBLIC_apiKey= 46 | NEXT_PUBLIC_authDomain= 47 | NEXT_PUBLIC_projectId= 48 | NEXT_PUBLIC_storageBucket= 49 | NEXT_PUBLIC_messagingSenderId= 50 | NEXT_PUBLIC_appId= 51 | ``` 52 | 53 | Enjoy! 🤘 54 | -------------------------------------------------------------------------------- /components/layout/header.tsx: -------------------------------------------------------------------------------- 1 | import { useAuth, signOut } from "../../lib/authContext"; 2 | import Link from "next/link"; 3 | 4 | export default function Header(props: any) { 5 | const { user, loading } = useAuth(); 6 | 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 |
14 | 15 |
16 | {!user && !loading ? ( 17 | <> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) : null} 27 | {user ? ( 28 | <> 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) : null} 40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-firebase-tailwindcss", 3 | "version": "12.3.1", 4 | "author": { 5 | "name": "Adel Chemi", 6 | "email": "adelchemi6@gmail.com", 7 | "url": "https://github.com/chemiadel" 8 | }, 9 | "private": false, 10 | "scripts": { 11 | "dev": "next dev", 12 | "build": "next build", 13 | "start": "next start", 14 | "lint": "next lint", 15 | "prettier": "prettier -w", 16 | "prepare": "husky install" 17 | }, 18 | "dependencies": { 19 | "firebase": "^9.12.1", 20 | "firebase-admin": "^9.11.1", 21 | "next": "^12.3.1", 22 | "nookies": "^2.5.2", 23 | "react": "17.0.2", 24 | "react-dom": "17.0.2" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "17.0.20", 28 | "autoprefixer": "^10.3.4", 29 | "eslint": "7.32.0", 30 | "eslint-config-airbnb": "^19.0.4", 31 | "eslint-config-import": "^0.13.0", 32 | "eslint-config-next": "^12.3.1", 33 | "eslint-config-prettier": "^8.5.0", 34 | "eslint-plugin-import": "^2.24.2", 35 | "eslint-plugin-jsx-a11y": "^6.4.1", 36 | "eslint-plugin-prettier": "^4.0.0", 37 | "eslint-plugin-react": "^7.25.1", 38 | "eslint-plugin-react-hooks": "^4.2.0", 39 | "husky": "^8.0.0", 40 | "lint-staged": "^13.0.3", 41 | "postcss": "^8.3.6", 42 | "prettier": "^2.7.1", 43 | "tailwindcss": "^2.2.15", 44 | "typescript": "4.4.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/authContext.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext, createContext } from "react"; 2 | import { getAuth, onAuthStateChanged, signOut as signout } from "firebase/auth"; 3 | import { setCookie, destroyCookie } from "nookies"; 4 | 5 | export type TIdTokenResult = { 6 | token: string; 7 | expirationTime: string; 8 | authTime: string; 9 | issuedAtTime: string; 10 | signInProvider: string | null; 11 | signInSecondFactor: string | null; 12 | claims: { 13 | [key: string]: any; 14 | }; 15 | }; 16 | 17 | type Props = { 18 | children: React.ReactNode; 19 | }; 20 | 21 | type UserContext = { 22 | user: TIdTokenResult | null; 23 | loading: boolean; 24 | }; 25 | 26 | const authContext = createContext({ 27 | user: null, 28 | loading: true, 29 | }); 30 | 31 | export default function AuthContextProvider({ children }: Props) { 32 | const [user, setUser] = useState(null); 33 | const [loading, setLoading] = useState(true); 34 | 35 | useEffect(() => { 36 | const auth = getAuth(); 37 | onAuthStateChanged(auth, (user) => { 38 | //user returned from firebase not the state 39 | if (user) { 40 | // Save token for backend calls 41 | user.getIdToken().then((token) => 42 | setCookie(null, "idToken", token, { 43 | maxAge: 30 * 24 * 60 * 60, 44 | path: "/", 45 | }) 46 | ); 47 | 48 | // Save decoded token on the state 49 | user.getIdTokenResult().then((result) => setUser(result)); 50 | } 51 | if (!user) setUser(null); 52 | setLoading(false); 53 | }); 54 | }, []); 55 | 56 | return ( 57 | 58 | {children} 59 | 60 | ); 61 | } 62 | 63 | export const useAuth = () => useContext(authContext); 64 | 65 | export const signOut = async () => { 66 | const auth = getAuth(); 67 | destroyCookie(null, "idToken"); 68 | await signout(auth); 69 | }; 70 | -------------------------------------------------------------------------------- /pages/signin.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import { 4 | getAuth, 5 | signInWithEmailAndPassword, 6 | GoogleAuthProvider, 7 | signInWithPopup, 8 | } from "firebase/auth"; 9 | import { useState } from "react"; 10 | import { useAuth } from "../lib/authContext"; 11 | 12 | const Home: NextPage = () => { 13 | const [email, setEmail] = useState(""); 14 | const [password, setPassword] = useState(""); 15 | const { user, loading } = useAuth(); 16 | 17 | if (loading) return null; 18 | 19 | if (user) return

U already logged

; 20 | 21 | const auth = getAuth(); 22 | 23 | function login() { 24 | signInWithEmailAndPassword(auth, email, password) 25 | .then((userCredential) => { 26 | // Signed in 27 | const user = userCredential.user; 28 | console.log("success", user); 29 | // ... 30 | }) 31 | .catch((error) => { 32 | const errorCode = error.code; 33 | const errorMessage = error.message; 34 | console.log("error", errorMessage); 35 | window.alert(errorMessage); 36 | }); 37 | } 38 | 39 | function loginWithGoogle() { 40 | const googleProvider = new GoogleAuthProvider(); 41 | 42 | signInWithPopup(auth, googleProvider) 43 | .then((result) => { 44 | // This gives you a Google Access Token. You can use it to access the Google API. 45 | const credential = GoogleAuthProvider.credentialFromResult(result); 46 | // const token = credential.accessToken; 47 | // The signed-in user info. 48 | const user = result.user; 49 | console.log("sign with google", user); 50 | // ... 51 | }) 52 | .catch((error) => { 53 | // Handle Errors here. 54 | const errorCode = error.code; 55 | const errorMessage = error.message; 56 | // The email of the user's account used. 57 | const email = error.email; 58 | // The AuthCredential type that was used. 59 | const credential = GoogleAuthProvider.credentialFromError(error); 60 | // ... 61 | }); 62 | } 63 | 64 | return ( 65 | <> 66 | 67 | Signin 68 | 69 | 70 |
71 |
72 | setEmail(e.target.value)} 75 | className="border border-current " 76 | /> 77 |
78 | setPassword(e.target.value)} 81 | className="border border-current " 82 | /> 83 |
84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 | ); 92 | }; 93 | 94 | export default Home; 95 | -------------------------------------------------------------------------------- /pages/signup.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import { 4 | getAuth, 5 | createUserWithEmailAndPassword, 6 | GoogleAuthProvider, 7 | signInWithPopup, 8 | } from "firebase/auth"; 9 | import { useState } from "react"; 10 | import { useAuth } from "../lib/authContext"; 11 | 12 | const Home: NextPage = () => { 13 | const { user, loading } = useAuth(); 14 | const [email, setEmail] = useState(""); 15 | const [password, setPassword] = useState(""); 16 | 17 | if (loading) return null; 18 | 19 | if (user) return

U already logged

; 20 | 21 | const auth = getAuth(); 22 | 23 | function createUserCredentials() { 24 | createUserWithEmailAndPassword(auth, email, password) 25 | .then((userCredential) => { 26 | // Signed in 27 | const user = userCredential.user; 28 | // ... 29 | console.log("success", user); 30 | }) 31 | .catch((error) => { 32 | const errorCode = error.code; 33 | const errorMessage = error.message; 34 | console.log("error", errorMessage); 35 | window.alert(errorMessage); 36 | // .. 37 | }); 38 | } 39 | 40 | function loginWithGoogle() { 41 | const googleProvider = new GoogleAuthProvider(); 42 | 43 | signInWithPopup(auth, googleProvider) 44 | .then((result) => { 45 | // This gives you a Google Access Token. You can use it to access the Google API. 46 | const credential = GoogleAuthProvider.credentialFromResult(result); 47 | // const token = credential.accessToken; 48 | // The signed-in user info. 49 | const user = result.user; 50 | console.log("sign with google", user); 51 | // ... 52 | }) 53 | .catch((error) => { 54 | // Handle Errors here. 55 | const errorCode = error.code; 56 | const errorMessage = error.message; 57 | // The email of the user's account used. 58 | const email = error.email; 59 | // The AuthCredential type that was used. 60 | const credential = GoogleAuthProvider.credentialFromError(error); 61 | // ... 62 | }); 63 | } 64 | 65 | return ( 66 | <> 67 | 68 | Signup 69 | 70 | 71 |
72 |
73 | setEmail(e.target.value)} 76 | className="border border-current " 77 | /> 78 |
79 | setPassword(e.target.value)} 82 | className="border border-current " 83 | /> 84 |
85 | 86 |
87 |
88 | 89 |
90 |
91 | 92 | ); 93 | }; 94 | 95 | export default Home; 96 | --------------------------------------------------------------------------------