# Reading "Getting Started with NextAuth.js"

These are my comments when reading the Getting Started (opens new window) guide of NextAuth.js for a Nextra lab. November 2024.

# Repo https://github.com/nextauthjs/next-auth-example/

In New Project (opens new window) the guide suggests to clone the repo https://next-auth-example.vercel.app/ (opens new window) and learn from it, so I cloned it.

In the main branch the version of next-auth is beta and next is latest. The example used the app folder instead of pages. The version of next installed at this time (November 2024) was:

I noticed the next-auth has no public documentation for the beta version.

There are 5 branches. The others 4 are stale. 2 test/* branches, one feat/* branch and one apple-sign-in branch using next 12. All of them can be used to learn.

Conclusion

Discarded the idea of using it as the main learning resource.

So, I follow with the section Existing Project (opens new window) path.

# Initial Steps

I started fron the nextra assignment for the fake student https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865 (opens new window).

Made a new branch guide from commit 526ce78 (opens new window) when I was on main: https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/tree/guide (opens new window)

Added the API route pages/api/auth/[...nextauth].js as explained at https://next-auth.js.org/getting-started/example#add-api-route (opens new window). See https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/blob/guide/pages/api/auth/[...nextauth].js (opens new window)

Bug

I forced the script "dev": "next -p 3000", to listen in port 3000 in the package.json, since I have found a bug in next-auth GitHub provider. The sigin page seems to have hardcoded the port to 3000 http://localhost:3000/api/auth/signin/github: https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/blob/guide/package.json#L6-L10 (opens new window)

# Add API route

When reading the section [Add API route](Add API route) for Nextra I've got errors that were fixed by changing the calls to GithubProvider and NextAuth by adding .default:

File pages/api/auth/[...nextauth].js







 







 

import NextAuth from "next-auth" // https://next-auth.js.org/getting-started/example#add-api-route
import GithubProvider from "next-auth/providers/github"

export const authOptions = {
  // Configure one or more authentication providers
  providers: [
    GithubProvider.default({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // ...add more providers here
  ]
}

export default NextAuth.default(authOptions)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

TIP

For an explanation of how Next.js API routes work see https://ull-pl.vercel.app/nextra-playground/authorization/next-auth-tutorial#creating-the-server-config (opens new window) at my PL Notes.

Behind the scenes, this code creates all the relevant OAuth API routes within /api/auth/* so that auth API requests can be handled by NextAuth.js. In this way, NextAuth.js stays in charge of the whole application's request/response flow. See https://ull-pl.vercel.app/nextra-playground/authorization/next-auth-tutorial#routes (opens new window)

OAuth explained

# SessionProvider component in pages/_app.jsx

I also changed the main file pages/_app.jsx to use the SessionProvider as explained at https://next-auth.js.org/getting-started/example#configure-shared-session-state (opens new window)

File pages/_app.jsx:






 



 


 

 



/* Old code
export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}
*/
import { SessionProvider } from "next-auth/react"

export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

See https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/blob/guide/pages/_app.jsx (opens new window)

# login-btn component

Added the login-btn component as explained at https://next-auth.js.org/getting-started/example#frontend---add-react-hook (opens new window)

File components/login-btn.jsx

 


 
 


 















import { useSession, signIn, signOut } from "next-auth/react"

export default function Component() {
  const { data: session, status: status } = useSession()
  if (status === "loading") {
    return <p>Loading...</p>
  }
  if (session) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    )
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

The component is used in the page pages/auth/login.mdx:

➜  nextra-solution git:(guide) cat pages/auth/login.mdx 
import LoginBtn from '@/components/login-btn'

<LoginBtn />
1
2
3
4

# useSession() NextAuth React Hook

TIP

The useSession() (opens new window) React Hook in the NextAuth.js client is the easiest way to check if someone is signed in.

useSession() returns an object containing two values: data and status:

data: This can be three values: Session / undefined / null.

  • when the session hasn't been fetched yet, data will be undefined
  • in case it failed to retrieve the session, data will be null
  • in case of success, data will be a Session object.

status: Is a enum mapping to three possible session states: "loading" | "authenticated" | "unauthenticated"

To wrap all the pages, make sure that <SessionProvider> is added to pages/_app.js.

See https://github.com/ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/blob/guide/components/login-btn.jsx (opens new window)

# signIn() method

TIP

Using the client side signIn() (opens new window) method ensures the user ends back on the page they started on after completing a sign in flow. It will also handle CSRF Tokens for you automatically when signing in with email.

By default, when calling the signIn() method with no arguments, you will be redirected to the NextAuth.js sign-in page.

If you want to skip that and get redirected to your provider's page immediately, call the signIn() method with the provider's id.

For example to sign in with GitHub:




 


import { signIn } from "next-auth/react"

export default () => (
  <button onClick={() => signIn("github")}>Sign in with GitHub</button>
)
1
2
3
4
5

The signIn() method receives a second argument, an object with options. The most common options are callbackUrl (opens new window) and redirect (opens new window).

# Specifying a callbackUrl

The callbackUrl specifies to which URL the user will be redirected after signing in. Defaults to the page URL the sign-in is initiated from.

Examples:

signIn(undefined, { callbackUrl: '/foo' }) // A relative url starting with a slash
signIn('google', { callbackUrl: 'http://localhost:3000/bar' }) // Or an absolute URL at the same host name,
signIn('email', { email, callbackUrl: 'http://localhost:3000/foo' })
signIn('credentials', { redirect: false, password: 'password' })   // Disable the redirect and handle the error on the same page.
signIn('email', { redirect: false, email: 'bill@fillmurray.com' }) // In such case signIn will return a Promise,
1
2
3
4
5

# Using the redirect: false option

If redirect is set to false, the signIn method will return a Promise that resolves to an object with the following properties:

{
  error: string | undefined // Error message if there was an error
  status: number // HTTP status code
  ok: boolean // `true` if the request was successful, `false` otherwise
  url: string | null // The URL the user should be redirected to or null `null` if there was an error
}
1
2
3
4
5
6

# Protecting an API Route (opens new window)

TIP

getServerSession

To protect an API Route, you can use the getServerSession() (opens new window) method.

# pages/api/restricted.js

Here is the pages/api/restricted.js API route.

➜ nextra-solution git:(guide) cat pages/api/restricted.js

 



 













import { getServerSession } from "next-auth/next"
import { authOptions } from "./auth/[...nextauth]"

export default async (req, res) => {
  const session = await getServerSession(req, res, authOptions)

  if (session) {
    res.send({
      content:
        "This is protected content. You can access this content because you are signed in.",
    })
  } else {
    res.send({
      error: "You must be signed in to view the protected content on this page.",
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# pages/auth/restricted.mdx

Here is the pages/auth/restricted.mdx page:

 








import Restricted from '@/components/restricted'

<Restricted />

## Public content

Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Nullam in dui mauris. Vivamus hendrerit arcu sed 
1
2
3
4
5
6
7
8

# components/restricted.jsx

And here is the client components/restricted.jsx component.

➜ nextra-solution git:(guide) cat components/restricted.jsx







 

 






















"use client"

import { useSession, signOut } from "next-auth/react" // https://next-auth.js.org/getting-started/client#usesession
import styles from './counters.module.css'

export default function User() {
  const { data: session, status } = useSession()

  if (status === "authenticated") {
    console.error("***********Session***********")
    console.error(session)
      return (<div> 
        <br />
        <hr />
        <br />
        <h2>I want to share with you some secrets ... </h2>
        <br />
        This is the user information:
        <ul>
          <li> 
            <img src={session.user.image} alt={session.user.name} width="32" height="32" />
          </li>
          <li><span>Email: {session.user.email}</span></li>
          <li>Name: {session.user.name}</li>
        </ul>     
    </div>)
  }

  return <a href="/api/auth/signin" className={ styles.button}>Sign in</a>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

That when authenticated, looks like this:

/images/nextra/api-restricted.png

# NEXTAUTH_SECRET

This environment variable is used to encrypt the NextAuth.js JWT, and to hash email verification tokens (opens new window). This environment . This is the default value for the secret option in NextAuth and Middleware. Alternatively, you can also set AUTH_SECRET, which is an alias.

TIP

The Auth.js cli

➜  nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) ✗ npx auth --help
Usage: auth [options] [command]

The CLI tool by Auth.js to supercharge your authentication workflows.

Options:
  -V, --version               output the version number
  -h, --help                  display help for command

Commands:
  ask [options]               Ask about docs, API, or auth concepts.
  init [options] [framework]  Initialize a project.
  secret [options]            Generate a random string and add it to the .env file.
  add [provider]              Register a new authentication provider
  help [command]              display help for command
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Here is an example of how to generate a secret:

➜  nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) ✗ npx auth secret --raw 
Zg6aLaJzvOLaLq+WF1kdOntYf887LU/oU8+GxQnakto=
1
2

Here is an example of .env.local file:

# When deploying to production, set the NEXTAUTH_URL environment variable to the canonical URL of your site.
NEXTAUTH_SECRET=your secret
GITHUB_ID=your id
GITHUB_SECRET=your gh secret
GITHUB_TOKEN=your gh token
1
2
3
4
5

# Middleware

The most simple usage is when you want to require authentication for your entire site. You can add a middleware.js file in the root of your project.

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat -n middleware.js

/* 
See https://nextjs.org/docs/pages/building-your-application/routing/middleware#matcher
*/
import AuthMiddleware from "next-auth/middleware"

export const middleware = AuthMiddleware.default // To get the function. Only for nextra

export const config = { 
  matcher: ["/protected/:path*"],
}
1
2
3
4
5
6
7
8
9
10

Notice the .default at the end of the import at line 6.

If a user is not logged in, the default behavior is to redirect them to the sign-in page.

You must set the same secret in the middleware that the one you used in the file pages/api/auth/[...nextauth].js when calling NextAuth.default(authOptions) The easiest way is to set the NEXTAUTH_SECRET environment variable. It will be picked up by both the NextAuth config, as well as the middleware.js config.

# pages/protected/secret.mdx

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat pages/protected/secret.mdx






 







import User from '@/components/user'

import LoginBtn from '@/components/login-btn'

# Middleware Protected Secrets

- This page is protected by the middleware!
- Only authorized users can see this

<User />

<LoginBtn />
1
2
3
4
5
6
7
8
9
10
11
12

When you visit the page and are authenticated, you will see the page:

/images/nextra/next-auth-middleware-protected-page.png

# Deploying to production (opens new window)

When deploying your site set the NEXTAUTH_URL environment variable to the canonical URL of the website.

NEXTAUTH_URL=https://example.com
1

That looks like this in Vercel:

/images/nextra/vercel-project-environment-variables-1.png (opens new window)

/images/nextra/vercel-project-environment-variables-2.png

# Building a Protected Dynamic mdx page with Nextra at Client Side Rendering Time

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat pages/clientSideProtectedExampleDynamicPage.mdx

import Styles from "@/components/UserRepos.module.css"
import { buildDynamicMDX } from 'nextra/remote'
import { arrayToMarkdownList } from '@/src/utils'
import AuthenticatedContent from '@/components/authenticatedContent'
import ShowFrontmatter from '@/components/showFrontmatter'
import DPage from '@/src/dynamicPage'

import Cat from '@/components/dcat';

export async function getServerSideProps() {
  try {
    const randomURL = 'http://www.randomnumberapi.com/api/v1.0/random?min=1&max=100&count=5' // if you move the fetcher function outside of the getStaticProps, 
    const fetcher = () => fetch(randomURL).then(res => res.json())  // you will get an error because fetcher is not defined
    const res = arrayToMarkdownList(await fetcher()) // More fetched content

    const props = await buildDynamicMDX(DPage(res))
    props.__nextra_dynamic_opts.frontMatter = {a: '4', b: '5'} 
    return { props }
  }
  catch (error) {
    console.error('Error fetching data:', error)
    return { props: { error: 'Failed to load data' } }
  }
}

# NextAuth.js Client Side Example

If data on a page is fetched using calls to secure API routes - i.e. routes which use 
`getSession()` or `getToken()` to access the session - you can use the `useSession` 
React Hook to secure pages.

See https://next-auth.js.org/tutorials/securing-pages-and-api-routes#client-side

---

<AuthenticatedContent dependencies={{Cat, ShowFrontmatter}}/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# components/authenticatedContent.js

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat components/authenticatedContent.jsx

import { useSession, getSession, signIn, signOut } from "next-auth/react"
import { RemoteContent } from 'nextra/components'
import Styles from "@/components/UserRepos.module.css"


export default function AuthenticatedContent({error, dependencies}) {
  const { data: session, status } = useSession()

  if (error) {
    return <div>Error: {error}</div>
  }

  if (status === "loading") {
    return <h2 className={Styles.h2}>Loading...</h2>
  }

  if (status === "unauthenticated") {
    return  (<div>
      <h2 className={Styles.h2}>Access Denied to the private content</h2>
      <button onClick={() => signIn("github")} className={Styles.button}>Sign in</button>
    </div>)
  }

  return (
    <>
      <h2 className={Styles.h2}>Protected Content</h2>
      <p className={Styles.p}>Your email is: {session.user.email}. You can view this page because you are signed in.</p>
      <button onClick={() => signOut()} className={Styles.button}>Sign out</button>

      <RemoteContent components={dependencies} />
    </>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# src/dynamicPage.js

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat src/dynamicPage.js

/* a function that is a template for a mdx page */
import { replaceQuotes } from '@/src/utils'

export default res => replaceQuotes(`
# buildDynamicMDX and RemoteContent

## Code example

Here is some code:

"""js copy
let a=4
""" 

## A \\"cat\\"

<Cat />

## Random numbers

${res}  

## Frontmatter Value

<ShowFrontmatter />
`)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# src/showFrontmatter.js

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat components/showFrontmatter.jsx

import { useConfig } from 'nextra-theme-docs'
import styles from '@/components/UserRepos.module.css'

export default function ShowFrontmatter() {
  const { frontMatter } = useConfig()
  return (
    <ul className={styles.list}>
      {
        Object.keys(frontMatter).map(
          k => (<li key={k} className={styles.listItem}>The frontmatter value of "{k}" is "{frontMatter[k]}" </li>)
        )
      }
    </ul>)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# src/utils.js

➜ nextra-casiano-rodriguez-leon-alu0100291865 git:(guide) cat src/utils.js

export const replaceQuotes = s => s.replace(/(\\")|(")/g, function(m, p1, p2) { return p1? '"' : "`" })
export const arrayToMarkdownList = res => res.map(n => `- ${n}`).join("\n") 
1
2

# Extensibility (opens new window)

NextAuth.js allows you to hook into various parts of the authentication flow via their built-in callbacks (opens new window).

For example, to pass a value from the sign-in to the frontend, client-side, you can use a combination of the session (opens new window) and jwt (opens new window) callback like so:

File pages/api/auth/[...nextauth].js

...
callbacks: {
  async jwt({ token, account }) {
    // Persist the OAuth access_token to the token right after signin
    if (account) {
      token.accessToken = account.access_token
    }
    return token
  },
  async session({ session, token, user }) {
    // Send properties to the client, like an access_token from a provider.
    session.accessToken = token.accessToken
    return session
  }
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Now whenever you call getSession or useSession, the data object which is returned will include the accessToken value.

components/accessToken.jsx

import { useSession, signIn, signOut } from "next-auth/react"
export default function Component() {
  const { data } = useSession()
  const { accessToken } = data
  return <div>Access Token: {accessToken}</div>
}
1
2
3
4
5
6
Last Updated: 15 days ago