Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example: with-firebase-authentication-serverless #10078

Merged
merged 25 commits into from Jan 20, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/with-firebase-authentication-serverless/.env
@@ -0,0 +1,22 @@

# For variables you need accessible at build time, add the variable to
# next.config.js. For secret values in local development, add the variable
# to .env.local, outside of source control.

# Update these with your Firebase app's values.
FIREBASE_AUTH_DOMAIN=my-example-app.firebaseapp.com
FIREBASE_CLIENT_EMAIL=my-example-app-email@example.com
FIREBASE_DATABASE_URL=https://my-example-app.firebaseio.com
FIREBASE_PROJECT_ID=my-example-app-id
FIREBASE_PUBLIC_API_KEY=MyExampleAppAPIKey123

# Create another file in this directory named ".env.local", which you
# should not include in source control. In .env.local, set these secret
# environment variables:

# Your Firebase private key.
# FIREBASE_PRIVATE_KEY=some-key-here

# Secrets used by cookie-session.
# SESSION_SECRET_CURRENT=someSecretValue
# SESSION_SECRET_PREVIOUS=anotherSecretValue
28 changes: 28 additions & 0 deletions examples/with-firebase-authentication-serverless/.gitignore
@@ -0,0 +1,28 @@
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
.env.*local

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

kmjennison marked this conversation as resolved.
Show resolved Hide resolved
# Local dotenv files. We're following the file structure used in
# create-react-app and documented in the Ruby dotenv:
# https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
.env.*local
51 changes: 51 additions & 0 deletions examples/with-firebase-authentication-serverless/README.md
@@ -0,0 +1,51 @@
# Example: Firebase authentication with a serverless API

## How to use

### Using `create-next-app`

Execute [`create-next-app`](https://github.com/zeit/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-serverless with-firebase-authentication-serverless-app
# or
yarn create next-app --example with-firebase-authentication-serverless with-firebase-authentication-serverless-app
```

### Download manually

Download the example:

```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase-authentication-serverless
cd with-firebase-authentication-serverless
```

Set up Firebase:

- Create a project at the [Firebase console](https://console.firebase.google.com/).
- 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` 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` file at the root of this project.
- Set the environment variables `SESSION_SECRET_CURRENT` and `SESSION_SECRET_PREVIOUS` in the `.env` file. (These are used by [`cookie-session`](https://github.com/expressjs/cookie-session/#secret).]

Install it and run:

```bash
npm install
npm run dev
# or
yarn
yarn dev
```

Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))

```bash
now
```

After `now` successfully deploys, a URL will for your site will be displayed. Copy that 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.

## The idea behind the example

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.
@@ -0,0 +1,46 @@
/* globals window */
import React, { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from '../utils/auth/initFirebase'

// 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',
}

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
36 changes: 36 additions & 0 deletions examples/with-firebase-authentication-serverless/env.js
@@ -0,0 +1,36 @@
// Responsible for setting environment variables.
// Note: this isn't strictly required for this example – you can
// inline your Firebase config or set environment variables howevever
// else you wish – but it's a convenient way to make sure the private
// key doesn't end up in source control.

const fs = require('fs')

const { NODE_ENV } = process.env
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
)
}

// Set env vars from appropiate `.env` files. We're following the
// file structure used in create-react-app and documented in the
// Ruby dotenv. See:
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotEnvPath = './.env'
const dotEnvFiles = [
`${dotEnvPath}.${NODE_ENV}.local`,
`${dotEnvPath}.${NODE_ENV}`,
// Don't include `.env.local` for the test environment.
NODE_ENV !== 'test' && `${dotEnvPath}.local`,
dotEnvPath,
].filter(Boolean)

dotEnvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
// eslint-disable-next-line global-require
require('dotenv').config({
path: dotenvFile,
})
}
})
12 changes: 12 additions & 0 deletions examples/with-firebase-authentication-serverless/next.config.js
@@ -0,0 +1,12 @@
require('./env.js')

module.exports = {
// Public, build-time env vars.
// https://nextjs.org/docs#build-time-configuration
env: {
FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL,
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
FIREBASE_PUBLIC_API_KEY: process.env.FIREBASE_PUBLIC_API_KEY,
},
}
23 changes: 23 additions & 0 deletions examples/with-firebase-authentication-serverless/package.json
@@ -0,0 +1,23 @@
{
"name": "with-firebase-auth-serverless",
"version": "1.0.0",
"scripts": {
"dev": "NODE_ENV=development next dev",
"build": "NODE_ENV=production next build",
"start": "NODE_ENV=production next start"
},
"dependencies": {
"cookie-session": "1.4.0",
"dotenv": "8.2.0",
"firebase": "^7.6.1",
"firebase-admin": "^8.9.0",
"isomorphic-unfetch": "^3.0.0",
"lodash": "4.17.15",
"next": "latest",
"prop-types": "15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-firebaseui": "4.0.0"
},
"devDependencies": {}
}
@@ -0,0 +1,55 @@
/* eslint react/no-danger: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import Document, { Html, Head, Main, NextScript } from 'next/document'

class CustomDocument extends Document {
render() {
// Store initial props from request data that we need to use again on
// the client. See:
// https://github.com/zeit/next.js/issues/3043#issuecomment-334521241
// https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
// Alternatively, you could use a store, like Redux.
const { AuthUserInfo } = this.props
return (
<Html>
<Head>
<script
id="__MY_AUTH_USER_INFO"
type="application/json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(AuthUserInfo, null, 2),
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

CustomDocument.getInitialProps = async ctx => {
// Get the AuthUserInfo object. This is set if the server-rendered page
// is wrapped in the `withAuthUser` higher-order component.
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)

const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps, AuthUserInfo }
}

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

export default CustomDocument
@@ -0,0 +1,37 @@
import commonMiddleware from '../../utils/middleware/commonMiddleware'
import { verifyIdToken } from '../../utils/auth/firebaseAdmin'

const handler = (req, res) => {
if (!req.body) {
return res.status(400)
}

const { token } = req.body

// Here, we decode the user's Firebase token and store it in a cookie. Use
// express-session (or similar) to store the session data server-side.
// An alternative approach is to use Firebase's `createSessionCookie`. See:
// https://firebase.google.com/docs/auth/admin/manage-cookies
// Firebase docs:
// "This is a low overhead operation. The public certificates are initially
// queried and cached until they expire. Session cookie verification can be
// done with the cached public certificates without any additional network
// requests."
// However, in a serverless environment, we shouldn't rely on caching, so
// it's possible Firebase's `verifySessionCookie` will make frequent network
// requests in a serverless context.
return verifyIdToken(token)
.then(decodedToken => {
req.session.decodedToken = decodedToken
req.session.token = token
return decodedToken
})
.then(decodedToken => {
return res.status(200).json({ status: true, decodedToken })
})
.catch(error => {
return res.status(500).json({ error })
})
}

export default commonMiddleware(handler)
@@ -0,0 +1,10 @@
import commonMiddleware from '../../utils/middleware/commonMiddleware'

const handler = (req, res) => {
// Destroy the session.
// https://github.com/expressjs/cookie-session#destroying-a-session
req.session = null
res.status(200).json({ status: true })
}

export default commonMiddleware(handler)
17 changes: 17 additions & 0 deletions examples/with-firebase-authentication-serverless/pages/auth.js
@@ -0,0 +1,17 @@
import React from 'react'
import FirebaseAuth from '../components/FirebaseAuth'

const Auth = () => {
return (
<div>
<p>Sign in</p>
<div>
<FirebaseAuth />
</div>
</div>
)
}

Auth.propTypes = {}

export default Auth
@@ -0,0 +1,24 @@
import React from 'react'
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>
)
}

Example.displayName = 'Example'

Example.propTypes = {}

Example.defaultProps = {}

export default Example