Next.js (Part 3)
Neil Haddley • November 7, 2021
API routes and next-auth
Get Request
API routes let you create an API endpoint inside a Next.js app.
To create an API endpoint inside a Next.js app add a file (or folder) to the /pages/api directory

Get Request
Dynamic API routes
API routes can be dynamic, just like regular Next.js pages

Dynamic API Route
Unprotected pages
User login is not required to access all of the pages.

Unprotected page
next-auth
next-auth can be used to prevent unauthorized access to protected api methods and protected pages.
$npm install next-auth
GitHub Id and GitHub Secret
Here I used GitHub as an authentication provider.

GitHub Developer Settings

Register a new OAuth application

Note the Client ID and Client Secret
.env
Maintain the Client ID and Client Secret values in an .env file
[...nextauth]
Add a [...nextauth] api method
_app.js
Add a 'next-auth/client' Provider to _app.js

Provider
Add code to prevent unauthorized access
Ensure that a valid session exists before returning articles or article details (using a REST API call or a web page request)

/api/articles is now protected

/api/articles/4 is now protected

page /protected is now protected (server-side)

page /protected is now protected (client-side)
Adding login and logout to home page
Adding "Sign In" and "Sign Out" to home page.

Sign in

Sign in with GitHub provider

Signed in

authorized

authorized

authorized
next-auth database (optional)
Specifying a database is optional if you don't need to persist user data or support email sign in. If you don't specify a database then JSON Web Tokens will be enabled for session storage and used to store session data.
To specify a database update the [...nextauth].js file (and a few environment variables).

accounts

users

session (with user id and provider)

pages
articles.js and pagesapiarticlesindex.js
TEXT
1// articles.js 2 3export const articles = [ 4 { 5 "userId": 1, 6 "id": 1, 7 "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", 8 "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" 9 }, 10 { 11 "userId": 1, 12 "id": 2, 13 "title": "qui est esse", 14 "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" 15 } 16] 17 18// /pages/api/articles/index.js 19import { articles } from '../../../articles' 20 21const index = async (req, res) => { 22 23 res.status(200).json(articles) 24 25} 26 27export default index
pagesapiarticlesid.js
TEXT
1// /pages/api/articles/[id].js 2import {articles} from '../../../articles' 3 4const article = async (req, res) => { 5 6 const { query: { id } } = req 7 8 const article = articles.find(a => a.id.toString() === id) 9 10 if (!article) { 11 res.status(404).json({ message: `article ${id} not found` }) 12 } 13 14 res.status(200).json(article) 15 16} 17 18export default article
pagesunprotected
TEXT
1import { articles } from '../articles' 2 3function index() { 4 5 return ( 6 <div> 7 <ul> 8 {articles.map(article => (<li key={article.id}>{article.title}</li>))} 9 </ul> 10 </div> 11 ) 12} 13 14export default index
.env
TEXT
1//.env 2NEXTAUTH_GITHUB_ID=e95a2816a93e7daabc6c 3NEXTAUTH_GITHUB_SECRET=<secret>
apiauth...nextauth.js
TEXT
1// /api/auth/[...nextauth].js 2import NextAuth from "next-auth"; 3import Providers from "next-auth/providers"; 4 5const options = { 6 // @link https://next-auth.js.org/configuration/providers 7 providers: [ 8 Providers.GitHub({ 9 clientId: process.env.NEXTAUTH_GITHUB_ID, 10 clientSecret: process.env.NEXTAUTH_GITHUB_SECRET, 11 }), 12 /*Providers.Google({ 13 clientId: process.env.NEXTAUTH_GOOGLE_ID, 14 clientSecret: process.env.NEXTAUTH_GOOGLE_SECRET, 15 }), 16 Providers.Facebook({ 17 clientId: process.env.NEXTAUTH_FACEBOOK_ID, 18 clientSecret: process.env.NEXTAUTH_FACEBOOK_SECRET, 19 }),*/ 20 ] 21} 22 23export default (req,res) => NextAuth(req,res,options)
_app.js
TEXT
1import { createGlobalStyle, ThemeProvider } from 'styled-components' 2import { Provider } from "next-auth/client"; 3 4const GlobalStyle = createGlobalStyle` 5 body { 6 margin: 0; 7 padding: 0; 8 box-sizing: border-box; 9 } 10` 11 12const theme = { 13 colors: { 14 primary: '#0070f3', 15 }, 16} 17 18export default function App({ Component, pageProps }) { 19 return ( 20 <> 21 <Provider session={pageProps.session}> 22 <GlobalStyle /> 23 <ThemeProvider theme={theme}> 24 <Component {...pageProps} /> 25 </ThemeProvider> 26 </Provider> 27 </> 28 ) 29}
pagesapiarticlesindex.js updated
TEXT
1import { articles } from '../../../articles' 2import { getSession } from 'next-auth/client' 3 4const index = async (req, res) => { 5 6 const session = await getSession({ req }); 7 if (session) { 8 res.status(200).json(articles) 9 } else { 10 res.status(401).json({ message: `please log in` }) 11 } 12 13} 14 15export default index
pagesapiarticlesid.js updated
TEXT
1import { articles } from '../../../articles' 2import { getSession } from 'next-auth/client' 3 4const article = async (req, res) => { 5 6 const session = await getSession({ req }); 7 if (session) { 8 const { query: { id } } = req 9 const article = articles.find(a => a.id.toString() === id) 10 if (!article) { 11 res.status(404).json({ message: `article ${id} not found` }) 12 } 13 res.status(200).json(article) 14 } else { 15 res.status(401).json({ message: `please log in` }) 16 } 17 18} 19 20export default article
pagesprotected.js
TEXT
1import { getSession } from "next-auth/client" 2import { articles } from '../articles' 3 4function index({ session, articles }) { 5 6 // If no session exists, display access denied message 7 if (!session) { return (<div>You need to be logged on</div>) } 8 9 return ( 10 <div> 11 <ul> 12 {articles.map(article => (<li key={article.id}>{article.title}</li>))} 13 </ul> 14 </div> 15 ) 16} 17 18export default index 19 20export const getServerSideProps = async (context) => { 21 22 const session = await getSession(context); 23 24 // If no session exists, display access denied message 25 if (!session) { 26 return { 27 props: { 28 session 29 } 30 } 31 } 32 33 return { 34 props: { 35 session, 36 articles 37 } 38 } 39}
pagesprotected.js
TEXT
1import { useSession } from "next-auth/client" 2import useSWR from 'swr' 3 4function index() { 5 6 const [ session, loading ] = useSession() 7 8 // When rendering client side don't display anything until loading is complete 9 if (typeof window !== 'undefined' && loading) return null 10 11 // If no session exists, display access denied message 12 if (!session) { return (<div>You need to be logged on</div>) } 13 14 15 const fetcher = (...args) => fetch(...args).then(res => res.json()) 16 17 const { data, error } = useSWR('/api/articles', fetcher) 18 19 if (error) return <div>failed to load</div> 20 if (!data) return <div>loading...</div> 21 22 return ( 23 <div> 24 <ul> 25 {data.map(article => (<li key={article.id}>{article.title}</li>))} 26 </ul> 27 </div> 28 ) 29 30} 31 32export default index
pagesindex.js
TEXT
1import { signIn, signOut, useSession } from 'next-auth/client' 2 3export default function Home() { 4 const [session, loading] = useSession() 5 6 return ( 7 <> 8 {!session && (<> 9 Not signed in 10 <br /> 11 <button onClick={signIn}>Sign In</button> 12 </>)} 13 {session && (<> 14 Signed in as {session.user.email} 15 <br /> 16 <button onClick={signOut}>Sign Out</button> 17 </>)} 18 </> 19 ) 20}
...nextauth.js
TEXT
1import NextAuth from "next-auth"; 2import { session } from "next-auth/client"; 3import Providers from "next-auth/providers"; 4 5const options = { 6 // @link https://next-auth.js.org/configuration/providers 7 providers: [ 8 Providers.GitHub({ 9 clientId: process.env.NEXTAUTH_GITHUB_ID, 10 clientSecret: process.env.NEXTAUTH_GITHUB_SECRET, 11 }), 12 /*Providers.Google({ 13 clientId: process.env.NEXTAUTH_GOOGLE_ID, 14 clientSecret: process.env.NEXTAUTH_GOOGLE_SECRET, 15 }), 16 Providers.Facebook({ 17 clientId: process.env.NEXTAUTH_FACEBOOK_ID, 18 clientSecret: process.env.NEXTAUTH_FACEBOOK_SECRET, 19 }),*/ 20 ], 21 database: process.env.DB_URL, 22 session: { 23 jwt: true 24 }, 25 jwt: { 26 secret: process.env.JWT_SECRET 27 }, 28 callbacks: { 29 async jwt(token, user, account, profile, isNewUser) { 30 console.log("token", token) 31 console.log("user", user) 32 console.log("account", account) 33 console.log("profile", profile) 34 console.log("isNewUser", isNewUser) 35 if (user && user.id) { 36 token.id = user.id 37 } 38 if (account && account.provider) { 39 token.provider = account.provider 40 } 41 return token 42 }, 43 async session(session, token) { 44 session.user.id = token.id 45 session.user.provider = token.provider 46 return session 47 } 48 } 49} 50 51export default (req, res) => NextAuth(req, res, options)
.env
TEXT
1NEXTAUTH_GITHUB_ID=e95a2816a93e7daabc6c 2NEXTAUTH_GITHUB_SECRET=<secret> 3 4DB_USER=<user> 5DB_PASSWORD=<password> 6DB_URL=mongodb+srv://$DB_USER:$DB_PASSWORD@cluster0.gdnd5.mongodb.net/nextauthDB?retryWrites=true&w=majority 7JWT_SECRET=<secret> 8NEXTAUTH_URL=http://localhost:3000