Initial commit from Create Next App

This commit is contained in:
2020-10-25 16:16:56 +00:00
commit c0202bdaf8
15 changed files with 8452 additions and 0 deletions

9
.env.local.example Normal file
View File

@@ -0,0 +1,9 @@
# Update these with your Firebase app's values.
FIREBASE_CLIENT_EMAIL=my-example-app-email@example.com
NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=MyExampleAppAPIKey123
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=my-example-app.firebaseapp.com
NEXT_PUBLIC_FIREBASE_DATABASE_URL=https://my-example-app.firebaseio.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=my-example-app-id
# Your Firebase private key.
FIREBASE_PRIVATE_KEY=some-key-here

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# Example: Firebase authentication with a serverless API
This example includes Firebase authentication and serverless [API routes](https://nextjs.org/docs/api-routes/introduction).
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
```bash
npx create-next-app --example with-firebase-authentication with-firebase-authentication-app
# or
yarn create next-app --example with-firebase-authentication with-firebase-authentication-app
```
## Configuration
Set up Firebase:
- Create a project at the [Firebase console](https://console.firebase.google.com/).
- Copy the contents of `.env.local.example` into a new file called `.env.local`
- Get your account credentials from the Firebase console at _Project settings > Service accounts_, where you can click on _Generate new private key_ and download the credentials as a json file. It will contain keys such as `project_id`, `client_email` and `client_id`. Set them as environment variables in the `.env.local` file at the root of this project.
- Get your authentication credentials from the Firebase console under _Project settings > General> Your apps_ Add a new web app if you don't already have one. Under _Firebase SDK snippet_ choose _Config_ to get the configuration as JSON. It will include keys like `apiKey`, `authDomain` and `databaseUrl`. Set the appropriate environment variables in the `.env.local` file at the root of this project.
- Go to **Develop**, click on **Authentication** and in the **Sign-in method** tab enable authentication for the app.
Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
After deploying, copy the deployment URL and navigate to your Firebase project's Authentication tab. Scroll down in the page to "Authorized domains" and add that URL to the list.

View File

@@ -0,0 +1,54 @@
/* globals window */
import { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from '../utils/auth/initFirebase'
import { setUserCookie } from '../utils/auth/userCookies'
import { mapUserData } from '../utils/auth/mapUserData'
// Init the Firebase app.
initFirebase()
const firebaseAuthConfig = {
signInFlow: 'popup',
// Auth providers
// https://github.com/firebase/firebaseui-web#configure-oauth-providers
signInOptions: [
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: false,
},
],
signInSuccessUrl: '/',
credentialHelper: 'none',
callbacks: {
signInSuccessWithAuthResult: async ({ user }, redirectUrl) => {
const userData = mapUserData(user)
setUserCookie(userData)
},
},
}
const FirebaseAuth = () => {
// Do not SSR FirebaseUI, because it is not supported.
// https://github.com/firebase/firebaseui-web/issues/213
const [renderAuth, setRenderAuth] = useState(false)
useEffect(() => {
if (typeof window !== 'undefined') {
setRenderAuth(true)
}
}, [])
return (
<div>
{renderAuth ? (
<StyledFirebaseAuth
uiConfig={firebaseAuthConfig}
firebaseAuth={firebase.auth()}
/>
) : null}
</div>
)
}
export default FirebaseAuth

8058
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "with-firebase-authentication",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"firebase": "^7.15.5",
"firebase-admin": "^8.12.1",
"js-cookie": "2.2.1",
"next": "latest",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-firebaseui": "4.1.0",
"swr": "0.2.3"
},
"license": "MIT"
}

17
pages/api/getFood.js Normal file
View File

@@ -0,0 +1,17 @@
import { verifyIdToken } from '../../utils/auth/firebaseAdmin'
const favoriteFoods = ['pizza', 'burger', 'chips', 'tortilla']
const getFood = async (req, res) => {
const token = req.headers.token
try {
await verifyIdToken(token)
return res.status(200).json({
food: favoriteFoods[Math.floor(Math.random() * favoriteFoods.length)],
})
} catch (error) {
return res.status(401).send('You are unauthorised')
}
}
export default getFood

14
pages/auth.js Normal file
View File

@@ -0,0 +1,14 @@
import FirebaseAuth from '../components/FirebaseAuth'
const Auth = () => {
return (
<div>
<p>Sign in</p>
<div>
<FirebaseAuth />
</div>
</div>
)
}
export default Auth

17
pages/example.js Normal file
View File

@@ -0,0 +1,17 @@
import Link from 'next/link'
const Example = (props) => {
return (
<div>
<p>
This page is static because it does not fetch any data or include the
authed user info.
</p>
<Link href={'/'}>
<a>Home</a>
</Link>
</div>
)
}
export default Example

63
pages/index.js Normal file
View File

@@ -0,0 +1,63 @@
import useSWR from 'swr'
import Link from 'next/link'
import { useUser } from '../utils/auth/useUser'
const fetcher = (url, token) =>
fetch(url, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json', token }),
credentials: 'same-origin',
}).then((res) => res.json())
const Index = () => {
const { user, logout } = useUser()
const { data, error } = useSWR(
user ? ['/api/getFood', user.token] : null,
fetcher
)
if (!user) {
return (
<>
<p>Hi there!</p>
<p>
You are not signed in.{' '}
<Link href={'/auth'}>
<a>Sign in</a>
</Link>
</p>
</>
)
}
return (
<div>
<div>
<p>You're signed in. Email: {user.email}</p>
<p
style={{
display: 'inline-block',
color: 'blue',
textDecoration: 'underline',
cursor: 'pointer',
}}
onClick={() => logout()}
>
Log out
</p>
</div>
<div>
<Link href={'/example'}>
<a>Another example page</a>
</Link>
</div>
{error && <div>Failed to fetch food!</div>}
{data && !error ? (
<div>Your favorite food is {data.food}.</div>
) : (
<div>Loading...</div>
)}
</div>
)
}
export default Index

View File

@@ -0,0 +1,24 @@
import * as admin from 'firebase-admin'
export const verifyIdToken = (token) => {
const firebasePrivateKey = process.env.FIREBASE_PRIVATE_KEY
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// https://stackoverflow.com/a/41044630/1332513
privateKey: firebasePrivateKey.replace(/\\n/g, '\n'),
}),
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
})
}
return admin
.auth()
.verifyIdToken(token)
.catch((error) => {
throw error
})
}

View File

@@ -0,0 +1,15 @@
import firebase from 'firebase/app'
import 'firebase/auth'
const config = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
}
export default function initFirebase() {
if (!firebase.apps.length) {
firebase.initializeApp(config)
}
}

View File

@@ -0,0 +1,8 @@
export const mapUserData = (user) => {
const { uid, email, xa, ya } = user
return {
id: uid,
email,
token: xa || ya,
}
}

63
utils/auth/useUser.js Normal file
View File

@@ -0,0 +1,63 @@
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from '../auth/initFirebase'
import {
removeUserCookie,
setUserCookie,
getUserFromCookie,
} from './userCookies'
import { mapUserData } from './mapUserData'
initFirebase()
const useUser = () => {
const [user, setUser] = useState()
const router = useRouter()
const logout = async () => {
return firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
router.push('/auth')
})
.catch((e) => {
console.error(e)
})
}
useEffect(() => {
// Firebase updates the id token every hour, this
// makes sure the react state and the cookie are
// both kept up to date
const cancelAuthListener = firebase.auth().onIdTokenChanged((user) => {
if (user) {
const userData = mapUserData(user)
setUserCookie(userData)
setUser(userData)
} else {
removeUserCookie()
setUser()
}
})
const userFromCookie = getUserFromCookie()
if (!userFromCookie) {
router.push('/')
return
}
setUser(userFromCookie)
return () => {
cancelAuthListener()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return { user, logout }
}
export { useUser }

19
utils/auth/userCookies.js Normal file
View File

@@ -0,0 +1,19 @@
import cookies from 'js-cookie'
export const getUserFromCookie = () => {
const cookie = cookies.get('auth')
if (!cookie) {
return
}
return JSON.parse(cookie)
}
export const setUserCookie = (user) => {
cookies.set('auth', user, {
// firebase id tokens expire in one hour
// set cookie expiry to match
expires: 1 / 24,
})
}
export const removeUserCookie = () => cookies.remove('auth')