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

Example with cookie auth #5821

Merged
merged 21 commits into from
Dec 14, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions examples/with-cookie-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[![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

In this example, we authenticate users and store a token in a cookie. The example only shows how the user session works, keeping a user logged in between pages.

This example is backend agnostic and uses [isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch) to do the API calls on the client and the server.

The repo includes a minimal passwordless backend built with [Micro](https://www.npmjs.com/package/micro) and it logs the user in with a GitHub username and saves the user id from the API call as token.

Session is syncronized across tabs. If you logout your session gets logged out on all the windows as well. We use the HOC `withAuthSync` for this.

The helper function `auth` helps to retrieve the token across pages and redirects the user if not token was found.
7 changes: 7 additions & 0 deletions examples/with-cookie-auth/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { send } = require('micro')

const index = async (req, res) => {
send(res, 200, '<h1>Hello from Micro on Now 2.0!</h2>')
}

module.exports = index
23 changes: 23 additions & 0 deletions examples/with-cookie-auth/api/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { json, send, createError } = require('micro')
const fetch = require('isomorphic-unfetch')

const login = async (req, res) => {
const { username } = await json(req)
const url = `https://api.github.com/users/${username}`

console.log(url)

try {
const response = await fetch(url)
if (response.ok) {
const js = await response.json()
send(res, 200, js)
} else {
send(res, response.status, response.statusText)
}
} catch (error) {
throw createError(error.statusCode, error.statusText)
}
}

module.exports = login
20 changes: 20 additions & 0 deletions examples/with-cookie-auth/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"isomorphic-unfetch": "^3.0.0",
"micro": "^9.3.3"
},
"devDependencies": {
"micro-dev": "^3.0.0"
},
"scripts": {
"start": "micro",
"dev": "micro-dev"
},
"keywords": [],
"author": "",
"license": "ISC"
}
31 changes: 31 additions & 0 deletions examples/with-cookie-auth/api/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { send, createError } = require('micro')
const fetch = require('isomorphic-unfetch')

const profile = async (req, res) => {
if (!('authorization' in req.headers)) {
throw createError(401, 'Authorization header missing')
}

const auth = await req.headers.authorization
const { token } = JSON.parse(auth)
const url = `https://api.github.com/user/${token}`

console.log(url)

try {
const response = await fetch(url)

if (response.ok) {
const js = await response.json()
// Need camelcase in the frontend
const data = Object.assign({}, { avatarUrl: js.avatar_url }, js)
send(res, 200, { data })
} else {
send(res, response.status, response.statusText)
}
} catch (error) {
throw createError(error.statusCode, error.statusText)
}
}

module.exports = profile
12 changes: 12 additions & 0 deletions examples/with-cookie-auth/now.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"name": "with-cookie-auth",
"builds": [
{ "src": "www/package.json", "use": "@now/next" },
{ "src": "api/*.js", "use": "@now/node" }
],
"routes": [
{ "src": "/api/(.*)", "dest": "/api/$1" },
{ "src": "/(.*)", "dest": "/www/$1" }
]
}
58 changes: 58 additions & 0 deletions examples/with-cookie-auth/www/components/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Link from 'next/link'
import { logout } from '../utils/auth'

const Header = props => (
<header>
<nav>
<ul>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/login'>
<a>Login</a>
</Link>
</li>
<li>
<Link href='/profile'>
<a>Profile</a>
</Link>
</li>
<li>
<button onClick={logout}>Logout</button>
</li>
</ul>
</nav>
<style jsx>{`
ul {
display: flex;
list-style: none;
margin-left: 0;
padding-left: 0;
}

li {
margin-right: 1rem;
}

li:first-child {
margin-left: auto;
}

a {
color: #fff;
text-decoration: none;
}

header {
padding: 0.2rem;
color: #fff;
background-color: #333;
}
`}</style>
</header>
)

export default Header
42 changes: 42 additions & 0 deletions examples/with-cookie-auth/www/components/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import Head from 'next/head'
import Header from './header'

const Layout = props => (
<React.Fragment>
<Head>
<title>With Cookies</title>
</Head>
<style jsx global>{`
*,
*::before,
*::after {
box-sizing: border-box;
}

body {
margin: 0;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, Noto Sans, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}

.container {
max-width: 65rem;
margin: 1.5rem auto;
padding-left: 1rem;
padding-right: 1rem;
}
`}</style>
<React.Fragment>
j0lvera marked this conversation as resolved.
Show resolved Hide resolved
<Header />

<main>
<div className='container'>{props.children}</div>
</main>
</React.Fragment>
</React.Fragment>
)

export default Layout
16 changes: 16 additions & 0 deletions examples/with-cookie-auth/www/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "with-cookie-auth",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"isomorphic-unfetch": "^3.0.0",
"js-cookie": "^2.2.0",
"next": "^7.0.2",
"next-cookies": "^1.0.4",
"react": "^16.6.3",
"react-dom": "^16.6.3"
}
}
29 changes: 29 additions & 0 deletions examples/with-cookie-auth/www/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import Layout from '../components/layout'

const Home = props => (
<Layout>
<h1>Cookie-based authentication example</h1>

<p>Steps to test the functionality:</p>

<ol>
<li>Click login and enter your GitHub username.</li>
<li>
Click home and click profile again, notice how your session is being
used through a token stored in a cookie.
</li>
<li>
Click logout and try to go to profile again. You'll get redirected to
the `/login` route.
</li>
</ol>
<style jsx>{`
li {
margin-bottom: 0.5rem;
}
`}</style>
</Layout>
)

export default Home
95 changes: 95 additions & 0 deletions examples/with-cookie-auth/www/pages/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component } from 'react'
import Layout from '../components/layout'
import { login } from '../utils/auth'

class Login extends Component {
getInitialProps ({ req }) {
const apiUrl = `https://${req.headers.host}/api/profile.js`

return { apiUrl }
}

constructor (props) {
super(props)

this.state = { username: '', error: '' }
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
j0lvera marked this conversation as resolved.
Show resolved Hide resolved

handleChange (event) {
this.setState({ username: event.target.value })
}

async handleSubmit (event) {
event.preventDefault()
const username = this.state.username
const url = this.props.apiUrl
login({ username, url }).catch(() =>
this.setState({ error: 'Login failed.' })
)
}

render () {
return (
<Layout>
<div className='login'>
<form onSubmit={this.handleSubmit}>
<label htmlFor='username'>GitHub username</label>

<input
type='text'
id='username'
name='username'
value={this.state.username}
onChange={this.handleChange}
/>

<button type='submit'>Login</button>

<p className={`error ${this.state.error && 'show'}`}>
{this.state.error && `Error: ${this.state.error}`}
</p>
</form>
</div>
<style jsx>{`
.login {
max-width: 340px;
margin: 0 auto;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}

form {
display: flex;
flex-flow: column;
}

label {
font-weight: 600;
}

input {
padding: 8px;
margin: 0.3rem 0 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}

.error {
margin: 0.5rem 0 0;
display: none;
color: brown;
}

.error.show {
display: block;
}
`}</style>
</Layout>
)
}
}

export default Login
Loading