Skip to content
This repository was archived by the owner on Oct 8, 2024. It is now read-only.

Commit 70c14bd

Browse files
committed
Initial commit from Create Next App
0 parents  commit 70c14bd

15 files changed

+6685
-0
lines changed

.env.local.example

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Update these with your Firebase app's values.
2+
3+
NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=MyExampleAppAPIKey123
4+
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=my-example-app.firebaseapp.com
5+
NEXT_PUBLIC_FIREBASE_DATABASE_URL=https://my-example-app.firebaseio.com
6+
NEXT_PUBLIC_FIREBASE_PROJECT_ID=my-example-app-id
7+
8+
# Your Firebase private key.
9+
FIREBASE_PRIVATE_KEY=some-key-here

.gitignore

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
27+
# local env files
28+
.env.local
29+
.env.development.local
30+
.env.test.local
31+
.env.production.local
32+
33+
# vercel
34+
.vercel

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Example: Firebase authentication with a serverless API
2+
3+
This example includes Firebase authentication and serverless [API routes](https://nextjs.org/docs/api-routes/introduction).
4+
5+
## How to use
6+
7+
### Using `create-next-app`
8+
9+
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:
10+
11+
```bash
12+
npx create-next-app --example with-firebase-authentication with-firebase-authentication-app
13+
# or
14+
yarn create next-app --example with-firebase-authentication with-firebase-authentication-app
15+
```
16+
17+
### Download manually
18+
19+
Download the example:
20+
21+
```bash
22+
curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase-authentication
23+
cd with-firebase-authentication
24+
```
25+
26+
## Configuration
27+
28+
Set up Firebase:
29+
30+
- Create a project at the [Firebase console](https://console.firebase.google.com/).
31+
- Copy the contents of `.env.local.example` into a new file called `.env.local`
32+
- 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.
33+
- 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.
34+
- Go to **Develop**, click on **Authentication** and in the **Sign-in method** tab enable authentication for the app.
35+
36+
Install it and run:
37+
38+
```bash
39+
npm install
40+
npm run dev
41+
# or
42+
yarn
43+
yarn dev
44+
```
45+
46+
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)).
47+
48+
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.

components/FirebaseAuth.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* globals window */
2+
import { useEffect, useState } from 'react'
3+
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
4+
import firebase from 'firebase/app'
5+
import 'firebase/auth'
6+
import initFirebase from '../utils/auth/initFirebase'
7+
import { setUserCookie } from '../utils/auth/userCookies'
8+
import { mapUserData } from '../utils/auth/mapUserData'
9+
10+
// Init the Firebase app.
11+
initFirebase()
12+
13+
const firebaseAuthConfig = {
14+
signInFlow: 'popup',
15+
// Auth providers
16+
// https://github.com/firebase/firebaseui-web#configure-oauth-providers
17+
signInOptions: [
18+
{
19+
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
20+
requireDisplayName: false,
21+
},
22+
],
23+
signInSuccessUrl: '/',
24+
credentialHelper: 'none',
25+
callbacks: {
26+
signInSuccessWithAuthResult: async ({ user }, redirectUrl) => {
27+
const userData = mapUserData(user)
28+
setUserCookie(userData)
29+
},
30+
},
31+
}
32+
33+
const FirebaseAuth = () => {
34+
// Do not SSR FirebaseUI, because it is not supported.
35+
// https://github.com/firebase/firebaseui-web/issues/213
36+
const [renderAuth, setRenderAuth] = useState(false)
37+
useEffect(() => {
38+
if (typeof window !== 'undefined') {
39+
setRenderAuth(true)
40+
}
41+
}, [])
42+
return (
43+
<div>
44+
{renderAuth ? (
45+
<StyledFirebaseAuth
46+
uiConfig={firebaseAuthConfig}
47+
firebaseAuth={firebase.auth()}
48+
/>
49+
) : null}
50+
</div>
51+
)
52+
}
53+
54+
export default FirebaseAuth

package.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "with-firebase-authentication",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"dev": "next dev",
6+
"build": "next build",
7+
"start": "next start"
8+
},
9+
"dependencies": {
10+
"firebase": "^7.15.5",
11+
"firebase-admin": "^8.12.1",
12+
"js-cookie": "2.2.1",
13+
"next": "latest",
14+
"react": "^16.13.1",
15+
"react-dom": "^16.13.1",
16+
"react-firebaseui": "4.1.0",
17+
"swr": "0.2.3"
18+
}
19+
}

pages/api/getFood.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { verifyIdToken } from '../../utils/auth/firebaseAdmin'
2+
const favoriteFoods = ['pizza', 'burger', 'chips', 'tortilla']
3+
4+
const getFood = async (req, res) => {
5+
const token = req.headers.token
6+
7+
try {
8+
await verifyIdToken(token)
9+
return res.status(200).json({
10+
food: favoriteFoods[Math.floor(Math.random() * favoriteFoods.length)],
11+
})
12+
} catch (error) {
13+
return res.status(401).send('You are unauthorised')
14+
}
15+
}
16+
17+
export default getFood

pages/auth.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import FirebaseAuth from '../components/FirebaseAuth'
2+
3+
const Auth = () => {
4+
return (
5+
<div>
6+
<p>Sign in</p>
7+
<div>
8+
<FirebaseAuth />
9+
</div>
10+
</div>
11+
)
12+
}
13+
14+
export default Auth

pages/example.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Link from 'next/link'
2+
3+
const Example = (props) => {
4+
return (
5+
<div>
6+
<p>
7+
This page is static because it does not fetch any data or include the
8+
authed user info.
9+
</p>
10+
<Link href={'/'}>
11+
<a>Home</a>
12+
</Link>
13+
</div>
14+
)
15+
}
16+
17+
export default Example

pages/index.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import useSWR from 'swr'
2+
import Link from 'next/link'
3+
import { useUser } from '../utils/auth/useUser'
4+
5+
const fetcher = (url, token) =>
6+
fetch(url, {
7+
method: 'GET',
8+
headers: new Headers({ 'Content-Type': 'application/json', token }),
9+
credentials: 'same-origin',
10+
}).then((res) => res.json())
11+
12+
const Index = () => {
13+
const { user, logout } = useUser()
14+
const { data, error } = useSWR(
15+
user ? ['/api/getFood', user.token] : null,
16+
fetcher
17+
)
18+
if (!user) {
19+
return (
20+
<>
21+
<p>Hi there!</p>
22+
<p>
23+
You are not signed in.{' '}
24+
<Link href={'/auth'}>
25+
<a>Sign in</a>
26+
</Link>
27+
</p>
28+
</>
29+
)
30+
}
31+
32+
return (
33+
<div>
34+
<div>
35+
<p>You're signed in. Email: {user.email}</p>
36+
<p
37+
style={{
38+
display: 'inline-block',
39+
color: 'blue',
40+
textDecoration: 'underline',
41+
cursor: 'pointer',
42+
}}
43+
onClick={() => logout()}
44+
>
45+
Log out
46+
</p>
47+
</div>
48+
<div>
49+
<Link href={'/example'}>
50+
<a>Another example page</a>
51+
</Link>
52+
</div>
53+
{error && <div>Failed to fetch food!</div>}
54+
{data && !error ? (
55+
<div>Your favorite food is {data.food}.</div>
56+
) : (
57+
<div>Loading...</div>
58+
)}
59+
</div>
60+
)
61+
}
62+
63+
export default Index

utils/auth/firebaseAdmin.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as admin from 'firebase-admin'
2+
3+
export const verifyIdToken = (token) => {
4+
const firebasePrivateKey = process.env.FIREBASE_PRIVATE_KEY
5+
6+
if (!admin.apps.length) {
7+
admin.initializeApp({
8+
credential: admin.credential.cert({
9+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
10+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
11+
// https://stackoverflow.com/a/41044630/1332513
12+
privateKey: firebasePrivateKey.replace(/\\n/g, '\n'),
13+
}),
14+
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
15+
})
16+
}
17+
18+
return admin
19+
.auth()
20+
.verifyIdToken(token)
21+
.catch((error) => {
22+
throw error
23+
})
24+
}

utils/auth/initFirebase.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import firebase from 'firebase/app'
2+
import 'firebase/auth'
3+
4+
const config = {
5+
apiKey: process.env.NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY,
6+
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
7+
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
8+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
9+
}
10+
11+
export default function initFirebase() {
12+
if (!firebase.apps.length) {
13+
firebase.initializeApp(config)
14+
}
15+
}

utils/auth/mapUserData.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const mapUserData = (user) => {
2+
const { uid, email, xa } = user
3+
return {
4+
id: uid,
5+
email,
6+
token: xa,
7+
}
8+
}

utils/auth/useUser.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useEffect, useState } from 'react'
2+
import { useRouter } from 'next/router'
3+
import firebase from 'firebase/app'
4+
import 'firebase/auth'
5+
import initFirebase from '../auth/initFirebase'
6+
import {
7+
removeUserCookie,
8+
setUserCookie,
9+
getUserFromCookie,
10+
} from './userCookies'
11+
import { mapUserData } from './mapUserData'
12+
13+
initFirebase()
14+
15+
const useUser = () => {
16+
const [user, setUser] = useState()
17+
const router = useRouter()
18+
19+
const logout = async () => {
20+
return firebase
21+
.auth()
22+
.signOut()
23+
.then(() => {
24+
// Sign-out successful.
25+
router.push('/auth')
26+
})
27+
.catch((e) => {
28+
console.error(e)
29+
})
30+
}
31+
32+
useEffect(() => {
33+
// Firebase updates the id token every hour, this
34+
// makes sure the react state and the cookie are
35+
// both kept up to date
36+
const cancelAuthListener = firebase.auth().onIdTokenChanged((user) => {
37+
if (user) {
38+
const userData = mapUserData(user)
39+
setUserCookie(userData)
40+
setUser(userData)
41+
} else {
42+
removeUserCookie()
43+
setUser()
44+
}
45+
})
46+
47+
const userFromCookie = getUserFromCookie()
48+
if (!userFromCookie) {
49+
router.push('/')
50+
return
51+
}
52+
setUser(userFromCookie)
53+
54+
return () => {
55+
cancelAuthListener()
56+
}
57+
// eslint-disable-next-line react-hooks/exhaustive-deps
58+
}, [])
59+
60+
return { user, logout }
61+
}
62+
63+
export { useUser }

0 commit comments

Comments
 (0)