-
Notifications
You must be signed in to change notification settings - Fork 27.5k
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 Magic authentication #11810
Merged
Merged
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
bb200e5
Add example with Magic and Passport.js
seanli c73d7dc
Tweaked wording on README
seanli 8180800
Fixed lint error
seanli b14534b
Merge branch 'canary' into magic
seanli 67a5093
Fixed prettier error
seanli 868cc70
Merge branch 'magic' of github.com:fortmatic/next.js into magic
seanli 16675ee
Update examples/magic/README.md
seanli 3cefa68
Merge remote-tracking branch 'upstream/canary' into magic
seanli 8b2308e
Removed dependency on passport and express + cleanup
seanli 2b4c748
Changed ZEIT brand to Vercel
seanli c84b8fa
Merge remote-tracking branch 'upstream/canary' into magic
seanli 1a4aaaa
Merge branch 'canary' into magic
seanli 4cdbd96
Updated readme instructions and secrets
b43e72b
Renamed example
a8168cb
Changed db comment
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
MAGIC_PUBLISHABLE_KEY= | ||
MAGIC_SECRET_KEY= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.env | ||
|
||
.now |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Magic Example | ||
|
||
This example show how to use [Magic](https://magic.link) with Next.js. The example features cookie-based, passwordless authentication with email-based magic links. | ||
|
||
The example shows how to do a login and logout; and to get the user info using a hook with [SWR](https://swr.now.sh). | ||
|
||
A DB is not included. You can use any db you want and add it [here](/lib/user.js). | ||
|
||
The login cookie is httpOnly, meaning it can only be accessed by the API, and it's encrypted using [@hapi/iron](https://hapi.dev/family/iron) for more security. | ||
|
||
## Configure Magic | ||
|
||
Login to the [Magic Dashboard](https://dashboard.magic.link/) and add new application to get your keys. Don't forget to put keys in `.env` (look for `.env.template` for example) and upload them as secrets to [ZEIT Now](https://zeit.co/now). | ||
|
||
``` | ||
now secrets add @magic-publishable-key pk_test_********* | ||
``` | ||
|
||
``` | ||
now secrets add @magic-secret-key sk_test_********* | ||
``` | ||
|
||
![Magic Dashboard](https://gblobscdn.gitbook.com/assets%2F-M1XNjqusnKyXZc7t7qQ%2F-M3HsSftOAghkNs-ttU3%2F-M3HsllfdwdDmeFXBK3U%2Fdashboard-pk.png?alt=media&token=4d6e7543-ae20-4355-951c-c6421b8f1b5f) | ||
|
||
## Deploy your own | ||
|
||
Deploy the example using [ZEIT Now](https://zeit.co/now): | ||
|
||
[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/new/project?template=https://github.com/zeit/next.js/tree/canary/examples/magic) | ||
|
||
## How to use | ||
|
||
### Using `create-next-app` | ||
|
||
Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: | ||
|
||
```bash | ||
npm init next-app --example magic magic-app | ||
# or | ||
yarn create next-app --example magic magic-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/magic | ||
cd magic | ||
``` | ||
|
||
Install it and run: | ||
|
||
```bash | ||
npm install | ||
npm run dev | ||
# or | ||
yarn | ||
yarn dev | ||
``` | ||
|
||
seanli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
const Form = ({ errorMessage, onSubmit }) => ( | ||
<form onSubmit={onSubmit}> | ||
<label> | ||
<span>Email</span> | ||
<input type="email" name="email" required /> | ||
</label> | ||
|
||
<div className="submit"> | ||
<button type="submit">Sign Up / Login</button> | ||
</div> | ||
|
||
{errorMessage && <p className="error">{errorMessage}</p>} | ||
|
||
<style jsx>{` | ||
form, | ||
label { | ||
display: flex; | ||
flex-flow: column; | ||
} | ||
label > span { | ||
font-weight: 600; | ||
} | ||
input { | ||
padding: 8px; | ||
margin: 0.3rem 0 1rem; | ||
border: 1px solid #ccc; | ||
border-radius: 4px; | ||
} | ||
.submit { | ||
display: flex; | ||
justify-content: flex-end; | ||
align-items: center; | ||
justify-content: space-between; | ||
} | ||
.submit > a { | ||
text-decoration: none; | ||
} | ||
.submit > button { | ||
padding: 0.5rem 1rem; | ||
cursor: pointer; | ||
background: #fff; | ||
border: 1px solid #ccc; | ||
border-radius: 4px; | ||
} | ||
.submit > button:hover { | ||
border-color: #888; | ||
} | ||
.error { | ||
color: brown; | ||
margin: 1rem 0 0; | ||
} | ||
`}</style> | ||
</form> | ||
) | ||
|
||
export default Form |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import Link from 'next/link' | ||
import { useUser } from '../lib/hooks' | ||
|
||
const Header = () => { | ||
const user = useUser() | ||
|
||
return ( | ||
<header> | ||
<nav> | ||
<ul> | ||
<li> | ||
<Link href="/"> | ||
<a>Home</a> | ||
</Link> | ||
</li> | ||
{user ? ( | ||
<> | ||
<li> | ||
<Link href="/profile"> | ||
<a>Profile</a> | ||
</Link> | ||
</li> | ||
<li> | ||
<a href="/api/logout">Logout</a> | ||
</li> | ||
</> | ||
) : ( | ||
<li> | ||
<Link href="/login"> | ||
<a>Login</a> | ||
</Link> | ||
</li> | ||
)} | ||
</ul> | ||
</nav> | ||
<style jsx>{` | ||
nav { | ||
max-width: 42rem; | ||
margin: 0 auto; | ||
padding: 0.2rem 1.25rem; | ||
} | ||
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 { | ||
color: #fff; | ||
background-color: #333; | ||
} | ||
`}</style> | ||
</header> | ||
) | ||
} | ||
|
||
export default Header |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import Head from 'next/head' | ||
import Header from './header' | ||
|
||
const Layout = props => ( | ||
<> | ||
<Head> | ||
<title>Magic</title> | ||
</Head> | ||
|
||
<Header /> | ||
|
||
<main> | ||
<div className="container">{props.children}</div> | ||
</main> | ||
|
||
<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: 42rem; | ||
margin: 0 auto; | ||
padding: 2rem 1.25rem; | ||
} | ||
`}</style> | ||
</> | ||
) | ||
|
||
export default Layout |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { serialize, parse } from 'cookie' | ||
|
||
const TOKEN_NAME = 'token' | ||
const MAX_AGE = 60 * 60 * 8 // 8 hours | ||
|
||
export function setTokenCookie(res, token) { | ||
const cookie = serialize(TOKEN_NAME, token, { | ||
maxAge: MAX_AGE, | ||
expires: new Date(Date.now() + MAX_AGE * 1000), | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === 'production', | ||
path: '/', | ||
sameSite: 'lax', | ||
}) | ||
res.setHeader('Set-Cookie', cookie) | ||
} | ||
|
||
export function removeTokenCookie(res) { | ||
const cookie = serialize(TOKEN_NAME, '', { | ||
maxAge: -1, | ||
path: '/', | ||
}) | ||
|
||
res.setHeader('Set-Cookie', cookie) | ||
} | ||
|
||
export function parseCookies(req) { | ||
// For API Routes we don't need to parse the cookies. | ||
if (req.cookies) return req.cookies | ||
|
||
// For pages we do need to parse the cookies. | ||
const cookie = req.headers?.cookie | ||
return parse(cookie || '') | ||
} | ||
|
||
export function getTokenCookie(req) { | ||
const cookies = parseCookies(req) | ||
return cookies[TOKEN_NAME] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { useEffect } from 'react' | ||
import Router from 'next/router' | ||
import useSWR from 'swr' | ||
|
||
const fetcher = url => | ||
fetch(url) | ||
.then(r => r.json()) | ||
.then(data => { | ||
return { user: data?.user || null } | ||
}) | ||
|
||
export function useUser({ redirectTo, redirectIfFound } = {}) { | ||
const { data, error } = useSWR('/api/user', fetcher) | ||
const user = data?.user | ||
const finished = Boolean(data) | ||
const hasUser = Boolean(user) | ||
|
||
useEffect(() => { | ||
if (!redirectTo || !finished) return | ||
if ( | ||
// If redirectTo is set, redirect if the user was not found. | ||
(redirectTo && !redirectIfFound && !hasUser) || | ||
// If redirectIfFound is also set, redirect if the user was found | ||
(redirectIfFound && hasUser) | ||
) { | ||
Router.push(redirectTo) | ||
} | ||
}, [redirectTo, redirectIfFound, finished, hasUser]) | ||
|
||
return error ? null : user | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Iron from '@hapi/iron' | ||
import { getTokenCookie } from './auth-cookies' | ||
|
||
// Use an environment variable here instead of a hardcoded value for production | ||
const TOKEN_SECRET = 'this-is-a-secret-value-with-at-least-32-characters' | ||
|
||
export function encryptSession(session) { | ||
return Iron.seal(session, TOKEN_SECRET, Iron.defaults) | ||
} | ||
|
||
export async function getSession(req) { | ||
const token = getTokenCookie(req) | ||
return token && Iron.unseal(token, TOKEN_SECRET, Iron.defaults) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { findUser } from './user' | ||
|
||
const { Magic } = require('@magic-sdk/admin') | ||
const magic = new Magic(process.env.MAGIC_SECRET_KEY) | ||
|
||
const MagicStrategy = require('passport-magic').Strategy | ||
|
||
export const strategy = new MagicStrategy(async function(user, done) { | ||
const userMetadata = await magic.users.getMetadataByIssuer(user.issuer) | ||
// In real application, if existing user doesn't exist, create new user based on email | ||
findUser({ email: userMetadata.email, issuer: userMetadata.issuer }) | ||
.then(user => { | ||
done(null, user) | ||
}) | ||
.catch(error => { | ||
done(error) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// import crypto from 'crypto' | ||
|
||
/** | ||
* User methods. The example doesn't contain a DB, but for real applications you must use a | ||
* db here, such as MongoDB, Fauna, SQL, etc. | ||
*/ | ||
|
||
export async function findUser({ email, issuer }) { | ||
// Here you should lookup for the user in your DB and compare the email: | ||
// | ||
// const user = await DB.findUser(...) | ||
|
||
return { email, issuer, createdAt: Date.now() } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const nextEnv = require('next-env') | ||
const dotenvLoad = require('dotenv-load') | ||
|
||
dotenvLoad() | ||
|
||
const withNextEnv = nextEnv() | ||
|
||
module.exports = withNextEnv({ | ||
env: { | ||
MAGIC_PUBLISHABLE_KEY: process.env.MAGIC_PUBLISHABLE_KEY, | ||
MAGIC_SECRET_KEY: process.env.MAGIC_SECRET_KEY, | ||
}, | ||
devIndicators: { | ||
autoPrerender: false, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"env": { | ||
"MAGIC_PUBLISHABLE_KEY": "@magic-publishable-key", | ||
"MAGIC_SECRET_KEY": "@magic-secret-key" | ||
}, | ||
"build": { | ||
"env": { | ||
"MAGIC_PUBLISHABLE_KEY": "@magic-publishable-key", | ||
"MAGIC_SECRET_KEY": "@magic-secret-key" | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Timer Should we remove this title too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lfades @Timer following up on this!~