├── .env.template ├── pages ├── api │ ├── me.js │ ├── login.js │ ├── logout.js │ ├── callback.js │ ├── billing-info.js │ └── register.js ├── about.js ├── billing-info.js ├── profile.js └── index.js ├── now.json ├── package.json ├── lib ├── auth0.js ├── use-api.js ├── config.js └── user.js ├── next.config.js ├── components ├── layout.js └── header.js ├── LICENSE ├── .gitignore └── README.md /.env.template: -------------------------------------------------------------------------------- 1 | AUTH0_DOMAIN= 2 | AUTH0_CLIENT_ID= 3 | AUTH0_CLIENT_SECRET= 4 | REDIRECT_URI= 5 | POST_LOGOUT_REDIRECT_URI= 6 | SESSION_COOKIE_SECRET= 7 | -------------------------------------------------------------------------------- /pages/api/me.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default async function me(req, res) { 4 | try { 5 | await auth0.handleProfile(req, res); 6 | } catch(error) { 7 | console.error(error) 8 | res.status(error.status || 500).end(error.message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pages/api/login.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default async function login(req, res) { 4 | try { 5 | await auth0.handleLogin(req, res); 6 | } catch(error) { 7 | console.error(error) 8 | res.status(error.status || 500).end(error.message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pages/api/logout.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default async function logout(req, res) { 4 | try { 5 | await auth0.handleLogout(req, res); 6 | } catch(error) { 7 | console.error(error) 8 | res.status(error.status || 500).end(error.message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pages/api/callback.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default async function callback(req, res) { 4 | try { 5 | await auth0.handleCallback(req, res, { redirectTo: '/' }); 6 | } catch(error) { 7 | console.error(error) 8 | res.status(error.status || 500).end(error.message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pages/api/billing-info.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default auth0.requireAuthentication(async function billingInfo(req, res) { 4 | const { user } = await auth0.getSession(req); 5 | res.json({ 6 | email: user.email, 7 | country: 'United States', 8 | paymentMethod: 'Paypal' 9 | }) 10 | }); 11 | -------------------------------------------------------------------------------- /pages/api/register.js: -------------------------------------------------------------------------------- 1 | import auth0 from '../../lib/auth0'; 2 | 3 | export default async function register(req, res) { 4 | try { 5 | await auth0.handleLogin(req, res, { 6 | authParams: { 7 | initialScreen: 'signup' 8 | } 9 | }); 10 | } catch(error) { 11 | console.error(error) 12 | res.status(error.status || 500).end(error.message) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "env": { 4 | "AUTH0_DOMAIN": "YOUR_AUTH0_DOMAIN", 5 | "AUTH0_CLIENT_ID": "YOUR_AUTH0_CLIENT_ID", 6 | "AUTH0_CLIENT_SECRET": "@auth0_client_secret", 7 | "REDIRECT_URI": "https://my-website.now.sh/api/callback", 8 | "POST_LOGOUT_REDIRECT_URI": "https://my-website.now.sh/", 9 | "SESSION_COOKIE_SECRET": "@session_cookie_secret" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-auth0-example", 3 | "version": "1.0.0", 4 | "description": "Example of how to sign in to your Next.js application using Auth0", 5 | "scripts": { 6 | "dev": "next", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@auth0/nextjs-auth0": "^0.6.0", 14 | "dotenv": "^8.2.0", 15 | "isomorphic-unfetch": "^3.0.0", 16 | "next": "^9.1.6", 17 | "react": "^16.12.0", 18 | "react-dom": "^16.12.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/auth0.js: -------------------------------------------------------------------------------- 1 | import { initAuth0 } from '@auth0/nextjs-auth0'; 2 | import config from './config'; 3 | 4 | export default initAuth0({ 5 | clientId: config.AUTH0_CLIENT_ID, 6 | clientSecret: config.AUTH0_CLIENT_SECRET, 7 | scope: config.AUTH0_SCOPE, 8 | domain: config.AUTH0_DOMAIN, 9 | redirectUri: config.REDIRECT_URI, 10 | postLogoutRedirectUri: config.POST_LOGOUT_REDIRECT_URI, 11 | session: { 12 | cookieSecret: config.SESSION_COOKIE_SECRET, 13 | cookieLifetime: config.SESSION_COOKIE_LIFETIME, 14 | storeIdToken: true, 15 | storeRefreshToken: true, 16 | storeAccessToken: true 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /pages/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Layout from '../components/layout'; 4 | import { useFetchUser } from '../lib/user'; 5 | 6 | export default function About() { 7 | const { user, loading } = useFetchUser(); 8 | 9 | return ( 10 | 11 |

About

12 |

13 | This is the about page, navigating between this page and Home is 14 | always pretty fast. However, when you navigate to the Profile page it takes more time because it uses SSR to 15 | fetch the user first; 16 |

17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | 3 | dotenv.config(); 4 | 5 | module.exports = { 6 | env: { 7 | AUTH0_DOMAIN: process.env.AUTH0_DOMAIN, 8 | AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, 9 | AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, 10 | AUTH0_SCOPE: 'openid profile offline_access', 11 | REDIRECT_URI: 12 | process.env.REDIRECT_URI || 'http://localhost:3000/api/callback', 13 | POST_LOGOUT_REDIRECT_URI: 14 | process.env.POST_LOGOUT_REDIRECT_URI || 'http://localhost:3000/', 15 | SESSION_COOKIE_SECRET: process.env.SESSION_COOKIE_SECRET, 16 | SESSION_COOKIE_LIFETIME: 7200 // 2 hours 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /pages/billing-info.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import useApi from '../lib/use-api'; 4 | import Layout from '../components/layout'; 5 | import { useFetchUser } from '../lib/user'; 6 | 7 | export default function Home() { 8 | const { user, loading } = useFetchUser(); 9 | const { response, error, isLoading } = useApi('/api/billing-info'); 10 | 11 | return ( 12 | 13 |

Billing Info

14 | 15 | {isLoading && ( 16 |

17 | Loading billing info... 18 |

19 | )} 20 | 21 | {!isLoading && ( 22 | <> 23 |
{JSON.stringify(response || error, null, 2)}
24 | 25 | )} 26 |
27 | ) 28 | }; 29 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | import Header from './header'; 4 | import { UserProvider } from '../lib/user'; 5 | 6 | const Layout = ({ user, loading = false, children }) => ( 7 | 8 | 9 | Next.js with Auth0 10 | 11 | 12 |
13 | 14 |
15 |
{children}
16 |
17 | 18 | 24 | 31 | 32 | ); 33 | 34 | export default Layout; 35 | -------------------------------------------------------------------------------- /lib/use-api.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-unfetch'; 2 | import React, { useEffect, useState } from 'react'; 3 | 4 | function initialState(args) { 5 | return { 6 | response: null, 7 | error: null, 8 | isLoading: true, 9 | ...args 10 | }; 11 | } 12 | 13 | export default (url, options = {}) => { 14 | const [state, setState] = useState(() => initialState()); 15 | 16 | useEffect(() => { 17 | const fetchData = async () => { 18 | try { 19 | const res = await fetch(url, { 20 | ...options 21 | }); 22 | 23 | setState(initialState({ 24 | response: await res.json(), 25 | isLoading: false 26 | })); 27 | } catch (error) { 28 | setState(initialState({ 29 | error 30 | })); 31 | } 32 | }; 33 | fetchData(); 34 | }, []); 35 | return state; 36 | }; -------------------------------------------------------------------------------- /pages/profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import auth0 from '../lib/auth0'; 4 | import { fetchUser } from '../lib/user'; 5 | import Layout from '../components/layout'; 6 | 7 | const Profile = ({ user }) => ( 8 | 9 |

Profile

10 | 11 |
12 |

Profile (server rendered)

13 |
{JSON.stringify(user, null, 2)}
14 |
15 |
16 | ); 17 | 18 | Profile.getInitialProps = async ({ req, res }) => { 19 | if (typeof window === 'undefined') { 20 | const session = await auth0.getSession(req); 21 | if (!session || !session.user) { 22 | res.writeHead(302, { 23 | Location: '/api/login' 24 | }); 25 | res.end(); 26 | return; 27 | } 28 | return { user: session.user }; 29 | } 30 | 31 | const user = await fetchUser(); 32 | return { user }; 33 | }; 34 | 35 | export default Profile; 36 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | if (typeof window === 'undefined') { 2 | /** 3 | * Settings exposed to the server. 4 | */ 5 | module.exports = { 6 | AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, 7 | AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, 8 | AUTH0_SCOPE: process.env.AUTH0_SCOPE, 9 | AUTH0_DOMAIN: process.env.AUTH0_DOMAIN, 10 | REDIRECT_URI: process.env.REDIRECT_URI, 11 | POST_LOGOUT_REDIRECT_URI: process.env.POST_LOGOUT_REDIRECT_URI, 12 | SESSION_COOKIE_SECRET: process.env.SESSION_COOKIE_SECRET, 13 | SESSION_COOKIE_LIFETIME: process.env.SESSION_COOKIE_LIFETIME 14 | }; 15 | } else { 16 | /** 17 | * Settings exposed to the client. 18 | */ 19 | module.exports = { 20 | AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, 21 | AUTH0_SCOPE: process.env.AUTH0_SCOPE, 22 | AUTH0_DOMAIN: process.env.AUTH0_DOMAIN, 23 | REDIRECT_URI: process.env.REDIRECT_URI, 24 | POST_LOGOUT_REDIRECT_URI: process.env.POST_LOGOUT_REDIRECT_URI 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Layout from '../components/layout'; 4 | import { useFetchUser } from '../lib/user'; 5 | 6 | export default function Home() { 7 | const { user, loading } = useFetchUser(); 8 | 9 | return ( 10 | 11 |

Next.js and Auth0 Example

12 | 13 | {loading && ( 14 |

15 | Loading login info... 16 |

17 | )} 18 | 19 | {!loading && !user && ( 20 | <> 21 |

22 | To test the login click in Login 23 |

24 |

25 | Once you have logged in you should be able to click in Profile and Logout 26 |

27 | 28 | )} 29 | 30 | {user && ( 31 | <> 32 |

Rendered user info on the client

33 |
{JSON.stringify(user, null, 2)}
34 | 35 | )} 36 |
37 | ) 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Distribution directories 61 | dist/ 62 | 63 | # Next.js 64 | .next 65 | -------------------------------------------------------------------------------- /lib/user.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import fetch from 'isomorphic-unfetch'; 3 | 4 | // Use a global to save the user, so we don't have to fetch it again after page navigations 5 | let userState; 6 | 7 | const User = React.createContext({ user: null, loading: false }); 8 | 9 | export const fetchUser = async () => { 10 | if (userState !== undefined) { 11 | return userState; 12 | } 13 | 14 | const res = await fetch('/api/me'); 15 | userState = res.ok ? await res.json() : null; 16 | return userState; 17 | } 18 | 19 | export const UserProvider = ({ value, children }) => { 20 | const { user } = value; 21 | 22 | // If the user was fetched in SSR add it to userState so we don't fetch it again 23 | React.useEffect(() => { 24 | if (!userState && user) { 25 | userState = user 26 | } 27 | }, []); 28 | 29 | return {children}; 30 | } 31 | 32 | export const useUser = () => React.useContext(User); 33 | 34 | export const useFetchUser = () => { 35 | const [data, setUser] = React.useState({ 36 | user: userState || null, 37 | loading: userState === undefined 38 | }); 39 | 40 | React.useEffect(() => { 41 | if (userState !== undefined) { 42 | return; 43 | } 44 | 45 | let isMounted = true; 46 | 47 | fetchUser().then(user => { 48 | // Only set the user if the component is still mounted 49 | if (isMounted) { 50 | setUser({ user, loading: false }); 51 | } 52 | }) 53 | 54 | return () => { 55 | isMounted = false 56 | }; 57 | }, [userState]); 58 | 59 | return data; 60 | } 61 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | import { useUser } from '../lib/user'; 4 | 5 | const Header = () => { 6 | const { user, loading } = useUser(); 7 | 8 | return ( 9 |
10 | 51 | 52 | 86 |
87 | ) 88 | } 89 | 90 | export default Header; 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js and Auth0 Example 2 | 3 | This example shows how you can use [`@auth0/nextjs-auth0`](https://github.com/auth0/nextjs-auth0) to easily add authentication support to your Next.js application. 4 | 5 | ## Configuring Auth0 6 | 7 | Go to the [Auth0 dashboard](https://manage.auth0.com/) and create a new application of type *Web Application* and make sure to configure the following: 8 | 9 | - *Allowed Callback URLs*: Should be set to `http://localhost:3000/api/callback` when testing locally or typically to `https://myapp.com/api/callback` when deploying your application. 10 | - *Allowed Logout URLs*: Should be set to `http://localhost:3000/` when testing locally or typically to `https://myapp.com/` when deploying your application. 11 | 12 | ### Configuring Next.js 13 | 14 | In the Next.js configuration file (`next.config.js`) you'll see that different environment variables are being loaded in the server runtime configuration. 15 | 16 | ### Local Development 17 | 18 | For local development you'll just want to create a `.env` file with the necessary settings: 19 | 20 | ``` 21 | AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN 22 | AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID 23 | AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET 24 | REDIRECT_URI=http://localhost:3000/api/callback 25 | POST_LOGOUT_REDIRECT_URI=http://localhost:3000/ 26 | SESSION_COOKIE_SECRET=viloxyf_z2GW6K4CT-KQD_MoLEA2wqv5jWuq4Jd0P7ymgG5GJGMpvMneXZzhK3sL (at least 32 characters, used to encrypt the cookie) 27 | ``` 28 | 29 | ### Hosting in Now.sh 30 | 31 | When deploying this example to Now.sh you'll want to update the `now.json` configuration file: 32 | 33 | ```json 34 | { 35 | "build": { 36 | "env": { 37 | "AUTH0_DOMAIN": "YOUR_AUTH0_DOMAIN", 38 | "AUTH0_CLIENT_ID": "YOUR_AUTH0_CLIENT_ID", 39 | "AUTH0_CLIENT_SECRET": "@auth0_client_secret", 40 | "REDIRECT_URI": "https://my-website.now.sh/api/callback", 41 | "POST_LOGOUT_REDIRECT_URI": "https://my-website.now.sh/", 42 | "SESSION_COOKIE_SECRET": "@session_cookie_secret", 43 | "SESSION_COOKIE_LIFETIME": 7200 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | Some of these values are settings and can just be added to your repository if you want. Others are actual secrets and need to be created as such using the `now` CLI: 50 | 51 | ```bash 52 | now secrets add auth0_client_secret YOUR_AUTH0_CLIENT_SECRET 53 | now secrets add session_cookie_secret viloxyf_z2GW6K4CT-KQD_MoLEA2wqv5jWuq4Jd0P7ymgG5GJGMpvMneXZzhK3sL 54 | ``` 55 | 56 | ## About this sample 57 | 58 | This sample tries to cover a few topics: 59 | 60 | - Signing in 61 | - Signing out 62 | - Registration 63 | - Loading the user on the server side and adding it as part of SSR (`/pages/profile.js`) 64 | - Loading the user on the client side and using fast/cached SSR pages (`/pages/index.js`) 65 | - API Routes which can load the current user (`/pages/api/me.js`) 66 | - Using hooks to make the user available throughout the application (`/lib//user.js`) 67 | 68 | ## Scenarios 69 | 70 | ### User Registration 71 | 72 | When the user is not signed in you'll see a Register and a Login link: 73 | 74 | ```js 75 |
  • 76 | Register 77 |
  • 78 |
  • 79 | Login 80 |
  • 81 | ``` 82 | 83 | The Register link redirects to the `/api/register` API route calling the login handler and passing a custom parameter which sets `prompt=signup`. 84 | 85 | ```js 86 | import auth0 from '../../lib/auth0'; 87 | 88 | export default async function register(req, res) { 89 | try { 90 | await auth0.handleLogin(req, res, { 91 | authParams: { 92 | initialScreen: 'signup' 93 | } 94 | }); 95 | } catch(error) { 96 | console.error(error) 97 | res.status(error.status || 500).end(error.message) 98 | } 99 | } 100 | ``` 101 | 102 | This parameter is then exposed to the Auth0 login page which can be used to show the signup tab. Note that this only works for the "Classic" experience today. In order to change to the signup tab when this parameter is sent, you'll need to modify the Login page in Auth0 and add the `initialScreen`: 103 | 104 | ```js 105 | var lock = new Auth0Lock(config.clientID, config.auth0Domain, { 106 | ... 107 | initialScreen: (config.extraParams.initialScreen === 'signup' && 'signUp') || 'login', 108 | ... 109 | }); 110 | ``` --------------------------------------------------------------------------------