Skip to content

Commit

Permalink
[Examples] remove getInitialProps from with-firebase-authentication (#…
Browse files Browse the repository at this point in the history
…13895)

Related to [11014](#11014)
  • Loading branch information
todortotev committed Jun 11, 2020
1 parent 1c057e7 commit 6c8b7cd
Show file tree
Hide file tree
Showing 21 changed files with 122 additions and 580 deletions.
13 changes: 0 additions & 13 deletions examples/with-firebase-authentication/.env.local.example

This file was deleted.

3 changes: 1 addition & 2 deletions examples/with-firebase-authentication/README.md
@@ -1,6 +1,6 @@
# Example: Firebase authentication with a serverless API

This example includes Firebase authentication and serverless [API routes](https://nextjs.org/docs/api-routes/introduction). On login, the app calls `/api/login`, which stores the user's info (their decoded Firebase token) in a cookie so that it's available server-side in `getInitialProps`. On logout, the app calls `/api/logout` to destroy the cookie.
This example includes Firebase authentication and serverless [API routes](https://nextjs.org/docs/api-routes/introduction).

## How to use

Expand Down Expand Up @@ -32,7 +32,6 @@ Set up Firebase:
- 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.
- Set the environment variables `SESSION_SECRET_CURRENT` and `SESSION_SECRET_PREVIOUS` in the `.env.local` file. (These are used by [`cookie-session`](https://github.com/expressjs/cookie-session/#secret).]

Install it and run:

Expand Down
16 changes: 16 additions & 0 deletions examples/with-firebase-authentication/components/FirebaseAuth.js
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
import cookie from 'js-cookie'
import initFirebase from '../utils/auth/initFirebase'

// Init the Firebase app.
Expand All @@ -20,6 +21,21 @@ const firebaseAuthConfig = {
],
signInSuccessUrl: '/',
credentialHelper: 'none',
callbacks: {
signInSuccessWithAuthResult: async ({ user }, redirectUrl) => {
// xa is the access token, which can be retrieved through
// firebase.auth().currentUser.getIdToken()
const { uid, email, xa } = user
const userData = {
id: uid,
email,
token: xa,
}
cookie.set('auth', userData, {
expires: 1,
})
},
},
}

const FirebaseAuth = () => {
Expand Down
10 changes: 4 additions & 6 deletions examples/with-firebase-authentication/package.json
Expand Up @@ -7,17 +7,15 @@
"start": "next start"
},
"dependencies": {
"cookie-session": "1.4.0",
"firebase": "^7.6.1",
"firebase-admin": "^8.9.0",
"lodash": "4.17.15",
"js-cookie": "2.2.1",
"next": "latest",
"next-cookies": "2.0.3",
"prop-types": "15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-firebaseui": "4.0.0"
},
"devDependencies": {
"dotenv": "8.2.0"
"react-firebaseui": "4.0.0",
"swr": "0.2.2"
}
}
55 changes: 0 additions & 55 deletions examples/with-firebase-authentication/pages/_document.js

This file was deleted.

17 changes: 17 additions & 0 deletions examples/with-firebase-authentication/pages/api/getFood.js
@@ -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
37 changes: 0 additions & 37 deletions examples/with-firebase-authentication/pages/api/login.js

This file was deleted.

10 changes: 0 additions & 10 deletions examples/with-firebase-authentication/pages/api/logout.js

This file was deleted.

2 changes: 0 additions & 2 deletions examples/with-firebase-authentication/pages/auth.js
Expand Up @@ -11,6 +11,4 @@ const Auth = () => {
)
}

Auth.propTypes = {}

export default Auth
6 changes: 0 additions & 6 deletions examples/with-firebase-authentication/pages/example.js
Expand Up @@ -14,10 +14,4 @@ const Example = (props) => {
)
}

Example.displayName = 'Example'

Example.propTypes = {}

Example.defaultProps = {}

export default Example
139 changes: 43 additions & 96 deletions examples/with-firebase-authentication/pages/index.js
@@ -1,113 +1,60 @@
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import useSWR from 'swr'
import Link from 'next/link'
import Router from 'next/router'
import withAuthUser from '../utils/pageWrappers/withAuthUser'
import withAuthUserInfo from '../utils/pageWrappers/withAuthUserInfo'
import logout from '../utils/auth/logout'

const Index = (props) => {
const { AuthUserInfo, data } = props
const AuthUser = get(AuthUserInfo, 'AuthUser', null)
const { favoriteFood } = data
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
)

return (
<div>
<p>Hi there!</p>
{!AuthUser ? (
if (!user) {
return (
<>
<p>Hi there!</p>
<p>
You are not signed in.{' '}
<Link href={'/auth'}>
<a>Sign in</a>
</Link>
</p>
) : (
<div>
<p>You're signed in. Email: {AuthUser.email}</p>
<p
style={{
display: 'inlinelock',
color: 'blue',
textDecoration: 'underline',
cursor: 'pointer',
}}
onClick={async () => {
try {
await logout()
Router.push('/auth')
} catch (e) {
console.error(e)
}
}}
>
Log out
</p>
</div>
)}
</>
)
}

return (
<div>
<div>
<p>You're signed in. Email: {user.email}</p>
<p
style={{
display: 'inlinelock',
color: 'blue',
textDecoration: 'underline',
cursor: 'pointer',
}}
onClick={() => logout()}
>
Log out
</p>
</div>
<div>
<Link href={'/example'}>
<a>Another example page</a>
</Link>
</div>
<div>
<div>Your favorite food is {favoriteFood}.</div>
</div>
{error && <div>Failed to fetch food!</div>}
{data && <div>Your favorite food is {data.food}.</div>}
</div>
)
}

// Just an example.
const mockFetchData = async (userId) => ({
user: {
...(userId && {
id: userId,
}),
},
favoriteFood: 'pizza',
})

Index.getInitialProps = async (ctx) => {
// Get the AuthUserInfo object. This is set in `withAuthUser.js`.
// The AuthUserInfo object is available on both the server and client.
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
const AuthUser = get(AuthUserInfo, 'AuthUser', null)

// You can also get the token (e.g., to authorize a request when fetching data)
// const AuthUserToken = get(AuthUserInfo, 'token', null)

// You can fetch data here.
const data = await mockFetchData(get(AuthUser, 'id'))

return {
data,
}
}

Index.displayName = 'Index'

Index.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}),
data: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
favoriteFood: PropTypes.string.isRequired,
}).isRequired,
}

Index.defaultProps = {
AuthUserInfo: null,
}

// Use `withAuthUser` to get the authed user server-side, which
// disables static rendering.
// Use `withAuthUserInfo` to include the authed user as a prop
// to your component.
export default withAuthUser(withAuthUserInfo(Index))
export default Index

6 comments on commit 6c8b7cd

@peterlee123hi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was great, but I'm sure you meant to remove .env.local.example

@ghost1998
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why .env.local.example is removed?

@mknoedel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really great! I wish I would have waited until today to start my latest project :P

@matteing
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be another place where the old version of this will be stored? Some sites just really need SSR -- and not having a resource to configure Firebase with it is going to be awful.

@kmjennison
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to set a cookie in this example, given that the Firebase user is only loaded client-side. Instead, it should just use the Firebase SDK. In fact, this example might be providing an expired Firebase ID token to the app if the token's stored in the cookie longer than an hour and isn't refreshed.

@kmjennison
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matteing I agree. Opened an issue here: #14783

Note that the previous Firebase SSR auth example (which this commit replaced) was flawed if you needed to use the Firebase ID token on the server side. The Firebase ID token was stored in a cookie and would expire after 1 hour, so often the server side wouldn't have a usable token. It worked fine if you only needed the Firebase user's ID or email, though.

Please sign in to comment.