diff --git a/examples/with-cookie-auth/README.md b/examples/with-cookie-auth/README.md
new file mode 100644
index 0000000000000..22d679a2b8140
--- /dev/null
+++ b/examples/with-cookie-auth/README.md
@@ -0,0 +1,43 @@
+[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-cookie-auth)
+# Example app utilizing cookie-based authentication
+
+## How to use
+
+### Using `create-next-app`
+
+Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
+
+```
+npm i -g create-next-app
+create-next-app --example with-cookie-auth with-cookie-auth-app
+```
+
+### Download manually
+
+Download the example [or clone the repo](https://github.com/zeit/next.js):
+
+```bash
+curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-cookie-auth
+cd with-cookie-auth
+```
+
+Install it and run:
+
+```bash
+npm install
+npm run dev
+```
+
+Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
+
+```bash
+now
+```
+
+## The idea behind the example
+
+The goal is to authenticate users and store basic info safely in a signed cookie.
+Certain pages can only be accessed by a logged-in user.
+
+> Use any email from https://jsonplaceholder.typicode.com/users to login, user website is the password.
+> e.g. Lucio_Hettinger@annie.ca / demarco.info
diff --git a/examples/with-cookie-auth/components/Layout.js b/examples/with-cookie-auth/components/Layout.js
new file mode 100644
index 0000000000000..5501f786da6cf
--- /dev/null
+++ b/examples/with-cookie-auth/components/Layout.js
@@ -0,0 +1,45 @@
+import React from 'react'
+import Link from 'next/link'
+import { processLogout } from '../lib/auth'
+
+const Layout = ({ auth, children }) => {
+ const { user = {} } = auth || {}
+ return (
+
+
Welcome, {user.name || 'Guest'}!
+
+ {children}
+
+
+ )
+}
+
+export default Layout
diff --git a/examples/with-cookie-auth/components/LoginForm.js b/examples/with-cookie-auth/components/LoginForm.js
new file mode 100644
index 0000000000000..501f2f38a91c3
--- /dev/null
+++ b/examples/with-cookie-auth/components/LoginForm.js
@@ -0,0 +1,50 @@
+import React from 'react'
+import Router from 'next/router'
+import { processLogin } from '../lib/auth'
+
+class LoginForm extends React.PureComponent {
+ state = {
+ email: '',
+ password: '',
+ error: undefined
+ }
+
+ onChange = ({ target: { name, value } }) => {
+ this.setState((state) => ({
+ [name]: value
+ }))
+ }
+
+ onSubmit = (e) => {
+ e.preventDefault()
+ const { email, password } = this.state
+ this.setState({ error: undefined })
+ processLogin({ email, password })
+ .then(() => Router.push('/profile'))
+ .catch(this.setError)
+ }
+
+ setError = (err) => {
+ console.warn({err})
+ const error = (err.response && err.response.data) || err.message
+ this.setState({ error })
+ }
+
+ render () {
+ const { email, password, error } = this.state
+ return (
+
+ )
+ }
+}
+
+export default LoginForm
diff --git a/examples/with-cookie-auth/lib/auth.js b/examples/with-cookie-auth/lib/auth.js
new file mode 100644
index 0000000000000..93f84adc333f6
--- /dev/null
+++ b/examples/with-cookie-auth/lib/auth.js
@@ -0,0 +1,97 @@
+import Router from 'next/router'
+import axios from 'axios'
+
+axios.defaults.withCredentials = true
+
+const WINDOW_USER_SCRIPT_VARIABLE = `__USER__`
+
+const decode = ({token}) => {
+ if (!token) {
+ return {}
+ }
+ const {email, type} = token || {}
+ return {user: {email, type}}
+}
+
+export const getUserScript = (user) => {
+ const json = JSON.stringify(user)
+ return `${WINDOW_USER_SCRIPT_VARIABLE} = ${json};`
+}
+
+export const getServerSideToken = (req) => {
+ const {signedCookies} = req
+
+ if (!signedCookies) {
+ return {}
+ }
+ try {
+ return decode(signedCookies)
+ } catch (parseError) {
+ return {}
+ }
+}
+
+export const getClientSideToken = () => {
+ if (typeof window !== 'undefined') {
+ const user = window[WINDOW_USER_SCRIPT_VARIABLE] || {}
+ return {user}
+ }
+ return {user: {}}
+}
+
+const getRedirectPath = (userType) => {
+ switch (userType) {
+ case 'authenticated':
+ return '/profile'
+ default:
+ return '/login'
+ }
+}
+
+const redirect = (res, path) => {
+ if (res) {
+ res.redirect(302, path)
+ res.finished = true
+ return {}
+ }
+ Router.replace(path)
+ return {}
+}
+
+export const authInitialProps = (redirectIfAuth, secured) => async ({req, res}) => {
+ const auth = req ? getServerSideToken(req) : getClientSideToken()
+ const current = req ? req.url : window.location.pathname
+ const user = auth.user
+ const isAnonymous = !user || user.type !== 'authenticated'
+ if (secured && isAnonymous && current !== '/login') {
+ return redirect(res, '/login')
+ }
+ if (!isAnonymous && redirectIfAuth) {
+ const path = getRedirectPath(user.type)
+ if (current !== path) {
+ return redirect(res, path)
+ }
+ }
+ return {auth}
+}
+
+export const getProfile = async () => {
+ const response = await axios.get('/api/profile')
+ return response.data
+}
+
+export const processLogin = async ({email, password}) => {
+ const response = await axios.post('/api/login', {email, password})
+ const {data} = response
+ if (typeof window !== 'undefined') {
+ window[WINDOW_USER_SCRIPT_VARIABLE] = data || {}
+ }
+}
+
+export const processLogout = async () => {
+ if (typeof window !== 'undefined') {
+ window[WINDOW_USER_SCRIPT_VARIABLE] = {}
+ }
+ await axios.post('/api/logout')
+ Router.push('/login')
+}
diff --git a/examples/with-cookie-auth/package.json b/examples/with-cookie-auth/package.json
new file mode 100644
index 0000000000000..f36468d67ed7a
--- /dev/null
+++ b/examples/with-cookie-auth/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "with-cookie-auth",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "node server.js",
+ "build": "next build",
+ "start": "NODE_ENV=production node server.js"
+ },
+ "dependencies": {
+ "axios": "^0.18.0",
+ "cookie-parser": "1.4.3",
+ "express": "^4.16.2",
+ "next": "latest",
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0"
+ },
+ "license": "ISC"
+}
diff --git a/examples/with-cookie-auth/pages/_document.js b/examples/with-cookie-auth/pages/_document.js
new file mode 100644
index 0000000000000..e558b8e3e4080
--- /dev/null
+++ b/examples/with-cookie-auth/pages/_document.js
@@ -0,0 +1,24 @@
+import Document, { Head, Main, NextScript } from 'next/document'
+import { getServerSideToken, getUserScript } from '../lib/auth'
+
+export default class extends Document {
+ static async getInitialProps (ctx) {
+ const props = await Document.getInitialProps(ctx)
+ const info = getServerSideToken(ctx.req)
+ return { ...props, ...info }
+ }
+
+ render () {
+ const { user = {} } = this.props
+ return (
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/examples/with-cookie-auth/pages/index.js b/examples/with-cookie-auth/pages/index.js
new file mode 100644
index 0000000000000..635c674835cc8
--- /dev/null
+++ b/examples/with-cookie-auth/pages/index.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import Link from 'next/link'
+import Layout from '../components/Layout'
+import { authInitialProps } from '../lib/auth'
+
+export default class Home extends React.PureComponent {
+ render () {
+ return (
+
+ Home
+ Try:
+
+
+ )
+ }
+}
+
+Home.getInitialProps = authInitialProps()
diff --git a/examples/with-cookie-auth/pages/login.js b/examples/with-cookie-auth/pages/login.js
new file mode 100644
index 0000000000000..8f13b6cffa225
--- /dev/null
+++ b/examples/with-cookie-auth/pages/login.js
@@ -0,0 +1,17 @@
+import React from 'react'
+import Layout from '../components/Layout'
+import { authInitialProps } from '../lib/auth'
+import LoginForm from '../components/LoginForm'
+
+export default class Login extends React.PureComponent {
+ render () {
+ return (
+
+ Login
+
+
+ )
+ }
+}
+
+Login.getInitialProps = authInitialProps(true)
diff --git a/examples/with-cookie-auth/pages/profile.js b/examples/with-cookie-auth/pages/profile.js
new file mode 100644
index 0000000000000..8041ceea5bfb3
--- /dev/null
+++ b/examples/with-cookie-auth/pages/profile.js
@@ -0,0 +1,23 @@
+import React from 'react'
+import Layout from '../components/Layout'
+import { authInitialProps, getProfile } from '../lib/auth'
+
+export default class Profile extends React.PureComponent {
+ state = { user: 'Loading...' }
+
+ componentDidMount () {
+ // to test withCredentials
+ getProfile().then(user => this.setState({ user }))
+ }
+
+ render () {
+ return (
+
+ Profile
+ {JSON.stringify(this.state.user, 0, 2)}
+
+ )
+ }
+}
+
+Profile.getInitialProps = authInitialProps(false, true)
diff --git a/examples/with-cookie-auth/server.js b/examples/with-cookie-auth/server.js
new file mode 100644
index 0000000000000..084ae96330ecd
--- /dev/null
+++ b/examples/with-cookie-auth/server.js
@@ -0,0 +1,78 @@
+const axios = require('axios')
+const express = require('express')
+const cookieParser = require('cookie-parser')
+const next = require('next')
+
+const dev = process.env.NODE_ENV !== 'production'
+
+const AUTHENTICATED_TYPE = 'authenticated'
+const COOKIE_SECRET = 'some deep dark secret'
+const COOKIE_OPTIONS = {
+ // domain: 'YOU_DOMAIN',
+ path: '/',
+ secure: !dev,
+ httpOnly: true,
+ signed: true
+}
+
+// fake serverless authentication
+const authenticate = async (email, password) => {
+ const users = await axios.get('https://jsonplaceholder.typicode.com/users').then(response => response.data)
+ const user = users.find(u => u.email.toLowerCase() === email.toLowerCase())
+ if (user && user.website === password) {
+ return user
+ }
+}
+
+const port = parseInt(process.env.PORT, 10) || 3000
+const app = next({ dev })
+const handle = app.getRequestHandler()
+
+app.prepare()
+ .then(() => {
+ const server = express()
+
+ server.use(express.json())
+
+ server.use(cookieParser(COOKIE_SECRET))
+
+ server.post('/api/login', async (req, res) => {
+ const { email, password } = req.body || {}
+ const user = await authenticate(email, password)
+ if (!user) {
+ return res.status(403).send('Invalid email or password')
+ }
+ const info = {
+ email: user.email,
+ name: user.name,
+ type: AUTHENTICATED_TYPE
+ }
+ res.cookie('token', info, COOKIE_OPTIONS)
+ return res.status(200).json(info)
+ })
+
+ server.post('/api/logout', (req, res) => {
+ res.clearCookie('token', COOKIE_OPTIONS)
+ return res.sendStatus(204)
+ })
+
+ server.get('/api/profile', async (req, res) => {
+ const { signedCookies = {} } = req
+ const { token = '' } = signedCookies
+ if (token && token.email) {
+ const users = await axios.get('https://jsonplaceholder.typicode.com/users').then(response => response.data)
+ const user = users.find(u => u.email.toLowerCase() === token.email.toLowerCase())
+ return res.status(200).json({ user })
+ }
+ return res.sendStatus(404)
+ })
+
+ server.get('*', (req, res) => {
+ return handle(req, res)
+ })
+
+ server.listen(port, (err) => {
+ if (err) throw err
+ console.log(`> Ready on http://localhost:${port}`)
+ })
+ })