├── .babelrc ├── .gitignore ├── README.md ├── components └── layout │ ├── MainLayout.tsx │ └── Navbar.tsx ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── callback.ts │ ├── login.ts │ ├── logout.ts │ └── me.ts ├── index.tsx └── profile.tsx ├── tsconfig.json └── utils ├── auth0.ts └── user.tsx /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | .env 4 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nextjs Auth0 Starter 2 | 3 | This is a starter project that integrates Auth0 with Next.js so that you can have a solid base to build your web application from. 4 | 5 | If you are just interested in the code, feel free to just use the latest commit. If you'd like to see how we get to this point, I posted a Youtube video that shows step-by-step how to add Auth0 to a new Next.js application: 6 | 7 | [How to add Auth0 to Nextjs- the Ultimate Guide](https://www.youtube.com/watch?v=vrj9gCSjzw0) 8 | 9 | **Features:** 10 | 11 | - Next.js 9.3 12 | - Ant Design 13 | - Auth0 using the nextjs-auth0 library 14 | - Typescript 15 | 16 | We start with a Typescript Next.js App and add Ant Design so that we have a nice UI layout to work with. 17 | 18 | Next we add a layout and a navbar with login, logout, home, and profile buttons. 19 | 20 | We utilize the nextjs-auth0 and the wonderful examples that they provide over on their [github page](https://github.com/auth0/nextjs-auth0) to create Next.js api endpoints for login, logout, redirect, and profile (thanks so much auth0!). 21 | 22 | Finally, we utilize a react.js provider component to house the user state information which allows us to access whether the user is logged in or not and their profile information. 23 | 24 | # How to use this repo 25 | 26 | 1. Clone this repo onto your computer and check out the `start-here` tag. 27 | 2. Check out the [How to add Auth0 to Nextjs- the Ultimate Guide](https://www.youtube.com/watch?v=vrj9gCSjzw0) youtube video that shows how to add Auth0 to a starter Next.js application. Once finished you'll have a fully working Next.js app with user login and profile management all set up. 28 | 3. ??? 29 | 4. Profit! 30 | 31 | Check out more related blog posts, courses and videos over at [Codemochi](https://codemochi.com). 32 | -------------------------------------------------------------------------------- /components/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from 'antd'; 2 | import { ReactNode, Component } from 'react'; 3 | import Navbar from './Navbar'; 4 | import styled from 'styled-components'; 5 | import { UserProvider, useFetchUser } from '../../utils/user'; 6 | 7 | const { Content } = Layout; 8 | 9 | const StyledContent = styled(Content)` 10 | min-height: 100vh; 11 | `; 12 | 13 | export const MainLayout = ({ children }: { children: ReactNode }) => { 14 | const { user, loading } = useFetchUser(); 15 | return ( 16 | 17 | 18 | 19 | {children} 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /components/layout/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Layout, Menu } from 'antd'; 2 | import Link from 'next/link'; 3 | import styled from 'styled-components'; 4 | import { useFetchUser } from '../../utils/user'; 5 | 6 | const { Header } = Layout; 7 | 8 | const StyledHeader = styled(Header)` 9 | background-color: #dddbe8; 10 | .ant-menu { 11 | width: 100%; 12 | background-color: #dddbe8; 13 | a { 14 | height: 64px; 15 | } 16 | } 17 | `; 18 | 19 | const Navbar = () => { 20 | const { user, loading } = useFetchUser(); 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | Home 28 | 29 | 30 | {user && !loading 31 | ? [ 32 | 33 | 34 | Logout 35 | 36 | , 37 | 38 | 39 | Profile 40 | 41 | , 42 | ] 43 | : null} 44 | {!user && !loading ? ( 45 | 46 | 47 | Login 48 | 49 | 50 | ) : null} 51 | 52 | 53 | ); 54 | }; 55 | 56 | export default Navbar; 57 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-auth0-starter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "dev": "next", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@auth0/nextjs-auth0": "^0.10.0", 15 | "antd": "^4.0.3", 16 | "dotenv": "^8.2.0", 17 | "isomorphic-unfetch": "^3.0.0", 18 | "next": "^9.3.1", 19 | "react": "^16.13.1", 20 | "react-dom": "^16.13.1", 21 | "styled-components": "^5.0.1" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^13.9.3", 25 | "@types/react": "^16.9.25", 26 | "typescript": "^3.8.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'antd/dist/antd.css'; 2 | import App from 'next/app'; 3 | 4 | class MyApp extends App { 5 | render() { 6 | const { Component, pageProps } = this.props; 7 | return ; 8 | } 9 | } 10 | 11 | export default MyApp; 12 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document from 'next/document'; 2 | import { ServerStyleSheet } from 'styled-components'; 3 | 4 | export default class MyDocument extends Document { 5 | static async getInitialProps(ctx) { 6 | const sheet = new ServerStyleSheet(); 7 | const originalRenderPage = ctx.renderPage; 8 | 9 | try { 10 | ctx.renderPage = () => 11 | originalRenderPage({ 12 | enhanceApp: App => props => sheet.collectStyles() 13 | }); 14 | 15 | const initialProps = await Document.getInitialProps(ctx); 16 | return { 17 | ...initialProps, 18 | styles: ( 19 | <> 20 | {initialProps.styles} 21 | {sheet.getStyleElement()} 22 | > 23 | ) 24 | }; 25 | } finally { 26 | sheet.seal(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pages/api/callback.ts: -------------------------------------------------------------------------------- 1 | import auth0 from '../../utils/auth0'; 2 | 3 | export default async function callback(req, res) { 4 | try { 5 | await auth0.handleCallback(req, res, {}); 6 | } catch (error) { 7 | console.error(error); 8 | res.status(error.status || 500).end(error.message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pages/api/login.ts: -------------------------------------------------------------------------------- 1 | import auth0 from '../../utils/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.ts: -------------------------------------------------------------------------------- 1 | import auth0 from '../../utils/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/me.ts: -------------------------------------------------------------------------------- 1 | import auth0 from '../../utils/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/index.tsx: -------------------------------------------------------------------------------- 1 | import { MainLayout } from '../components/layout/MainLayout'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledHome = styled.div` 5 | padding: 50px 10px; 6 | text-align: center; 7 | h1 { 8 | font-size: 60px; 9 | } 10 | `; 11 | 12 | export default function Index() { 13 | return ( 14 | 15 | 16 | 🏠 17 | Welcome to the Home Page! 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /pages/profile.tsx: -------------------------------------------------------------------------------- 1 | import { MainLayout } from '../components/layout/MainLayout'; 2 | import styled from 'styled-components'; 3 | import { useFetchUser } from '../utils/user'; 4 | import Router from 'next/router'; 5 | 6 | const StyledProfile = styled.div` 7 | padding: 50px 10px; 8 | text-align: center; 9 | h1 { 10 | font-size: 60px; 11 | } 12 | `; 13 | 14 | export default function Profile() { 15 | const { user, loading } = useFetchUser(); 16 | 17 | if (loading) { 18 | return ( 19 | 20 | Loading... 21 | 22 | ); 23 | } 24 | if (!user && !loading) { 25 | Router.replace('/'); 26 | } 27 | 28 | return ( 29 | 30 | 31 | 🤸 32 | Welcome to the Profile Page! Here is your profile information: 33 | {JSON.stringify(user)} 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "include": [ 25 | "next-env.d.ts", 26 | "**/*.ts", 27 | "**/*.tsx" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /utils/auth0.ts: -------------------------------------------------------------------------------- 1 | import { initAuth0 } from '@auth0/nextjs-auth0'; 2 | 3 | export default initAuth0({ 4 | domain: process.env.domain, 5 | clientId: process.env.clientId, 6 | clientSecret: process.env.clientSecret, 7 | scope: 'openid profile', 8 | redirectUri: process.env.redirectUri, 9 | postLogoutRedirectUri: process.env.postLogoutRedirectUri, 10 | session: { 11 | cookieSecret: process.env.cookieSecret, 12 | cookieLifetime: 60 * 60 * 8, 13 | storeIdToken: false, 14 | storeAccessToken: false, 15 | storeRefreshToken: false, 16 | }, 17 | oidcClient: { 18 | httpTimeout: 2500, 19 | clockTolerance: 10000, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /utils/user.tsx: -------------------------------------------------------------------------------- 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 | --------------------------------------------------------------------------------
Welcome to the Home Page!
Loading...
Welcome to the Profile Page! Here is your profile information:
{JSON.stringify(user)}