Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 6 additions & 25 deletions examples/with-cookie-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,12 @@ cd with-cookie-auth

### Run locally

The repository is setup as a [monorepo](https://zeit.co/examples/monorepo/) so you can run start the development server with `now dev` inside the project folder.

Install the packages of `/api` and `/www` using `npm` or `yarn`:

```bash
cd api
npm install
cd ../www
npm install
```

Now you can start the development server in the root folder:

```bash
now dev
```

You can configure the `API_URL` environment variable (defaults to `http://localhost:3000`) with [Now env](https://zeit.co/docs/v2/development/environment-variables/#using-now.json) in the `now.json` file:
After you clone the repository you can install the dependencies, run `yarn dev` and start hacking! You'll be able to see the application running locally as if it were deployed.

```bash
"build": {
"env": {
"API_URL": "https://example.com"
}
},
$ cd with-cookie-auth
$ (with-cookie-auth/) yarn install
$ (with-cookie-auth/) yarn dev
```

### Deploy
Expand All @@ -63,8 +44,8 @@ In this example, we authenticate users and store a token in a cookie. The exampl

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) that logs the user in with a GitHub username and saves the user id from the API call as token.
The repo includes a minimal passwordless backend built with the new [API Routes support](https://github.com/zeit/next.js/pull/7296) (`pages/api`), [Micro](https://www.npmjs.com/package/micro) and the [GitHub API](https://developer.github.com/v3/). The backend allows the user to log in with their GitHub username.

Session is synchronized across tabs. If you logout your session gets logged out on all the windows as well. We use the HOC `withAuthSync` for this.
Session is synchronized across tabs. If you logout your session gets removed 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.
21 changes: 0 additions & 21 deletions examples/with-cookie-auth/api/login.js

This file was deleted.

12 changes: 0 additions & 12 deletions examples/with-cookie-auth/api/package.json

This file was deleted.

29 changes: 0 additions & 29 deletions examples/with-cookie-auth/api/profile.js

This file was deleted.

3 changes: 3 additions & 0 deletions examples/with-cookie-auth/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
target: 'serverless'
}
17 changes: 0 additions & 17 deletions examples/with-cookie-auth/now.json

This file was deleted.

15 changes: 13 additions & 2 deletions examples/with-cookie-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
{
"name": "with-cookie-auth",
"version": "1.0.0",
"description": "Monorepo with a Next.js example of cookie based authorization"
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"isomorphic-unfetch": "^3.0.0",
"js-cookie": "^2.2.0",
"next": "^9.0.1",
"next-cookies": "^1.0.4",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}
26 changes: 26 additions & 0 deletions examples/with-cookie-auth/pages/api/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import fetch from 'isomorphic-unfetch'

export default async (req, res) => {
const { username } = await req.body
console.log('username', username)
const url = `https://api.github.com/users/${username}`

try {
const response = await fetch(url)

if (response.ok) {
const { id } = await response.json()
return res.status(200).json({ token: id })
} else {
// https://github.com/developit/unfetch#caveats
const error = new Error(response.statusText)
error.response = response
throw error
}
} catch (error) {
const { response } = error
return response
? res.status(response.status).json({ message: response.statusText })
: res.status(400).json({ message: error.message })
}
}
33 changes: 33 additions & 0 deletions examples/with-cookie-auth/pages/api/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import fetch from 'isomorphic-unfetch'

export default async (req, res) => {
if (!('authorization' in req.headers)) {
return res.status(401).send('Authorization header missing')
}

const auth = await req.headers.authorization

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

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)
return res.status(200).json({ data })
} else {
// https://github.com/developit/unfetch#caveats
const error = new Error(response.statusText)
error.response = response
throw error
}
} catch (error) {
const { response } = error
return response
? res.status(response.status).json({ message: response.statusText })
: res.status(400).json({ message: error.message })
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import Layout from '../components/layout'

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

Expand Down
105 changes: 105 additions & 0 deletions examples/with-cookie-auth/pages/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState } from 'react'
import fetch from 'isomorphic-unfetch'
import Layout from '../components/layout'
import { login } from '../utils/auth'

function Login () {
const [userData, setUserData] = useState({ username: '', error: '' })

async function handleSubmit (event) {
event.preventDefault()
setUserData(Object.assign({}, userData, { error: '' }))

const username = userData.username
const url = '/api/login'

try {
const response = await fetch(url, {
method: 'POST',

headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
})
if (response.status === 200) {
const { token } = await response.json()
await login({ token })
} else {
console.log('Login failed.')
// https://github.com/developit/unfetch#caveats
let error = new Error(response.statusText)
error.response = response
throw error
}
} catch (error) {
console.error(
'You have an error in your code or there are Network issues.',
error
)

const { response } = error
setUserData(
Object.assign({}, userData, {
error: response ? response.statusText : error.message
})
)
}
}

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

<input
type='text'
id='username'
name='username'
value={userData.username}
onChange={event =>
setUserData(
Object.assign({}, userData, { username: event.target.value })
)
}
/>

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

{userData.error && <p className='error'>Error: {userData.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;
color: brown;
}
`}</style>
</Layout>
)
}

export default Login
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react'
import Router from 'next/router'
import fetch from 'isomorphic-unfetch'
import nextCookie from 'next-cookies'
import Layout from '../components/layout'
import { withAuthSync } from '../utils/auth'
import getHost from '../utils/get-host'

const Profile = props => {
const { name, login, bio, avatarUrl } = props.data
Expand Down Expand Up @@ -41,28 +43,29 @@ const Profile = props => {

Profile.getInitialProps = async ctx => {
const { token } = nextCookie(ctx)
const url = `${process.env.API_URL}/api/profile.js`
const apiUrl = getHost(ctx.req) + '/api/profile'

const redirectOnError = () =>
typeof window !== 'undefined'
? Router.push('/login')
: ctx.res.writeHead(302, { Location: '/login' }).end()

try {
const response = await fetch(url, {
const response = await fetch(apiUrl, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authorization: JSON.stringify({ token })
}
})

if (response.ok) {
return await response.json()
const js = await response.json()
console.log('js', js)
return js
} else {
// https://github.com/developit/unfetch#caveats
return await redirectOnError()
}

// https://github.com/developit/unfetch#caveats
return redirectOnError()
} catch (error) {
// Implementation or Network error
return redirectOnError()
Expand Down
Loading