Authentication in NextJS

Authentication Scheme

Authentication means verifying that user is valid. Different providers are used for authemtication as mentioned below.
  • [userid + password==credential provider]
  • [social media login - goog/fb/github provider - Oauth2]
  • Email Provider
To make the application stateful, we need either session id or token session id
  • - After login, server creates sessions and sends session id to the client via cookie. User details are mapped to session id on server
  • - Browser automatically sends the session id via cookie on every subsequent request
  • - you can invalidate session
  • - Generally used in banking apps
token based authentication - JWT
  • - After successful login, server creates JWT token and sends to the client.
  • - Client can send the token in authorization header on every request.
  • - Nothing stored on server.
  • - need refresh token to keep user logged in

Using libraries

Passportjs - just a library to above things next-auth

Using auth service

auth0 - Amazon incognito

Next-Auth

High level steps are mentioned below.
  • Dependencies - next-auth and mongodb
  • Providers - Generate provider (google, github or facebook) client id and secret
  • next auth handler endpoint - Add api/auth/[...nextauth].js endpoint
  • Then you should be able to go to this sign in page at api/auth/signin
  • Add SessionProvider in _app.tsx
  • Use session at client side
  • Use session at Server Side
  • Use adapter if you want to store session and user data in db e.g. mongodb

Dependencies

First you need to install below dependencies

npm i --save next-auth mongodb  @next-auth/mongodb-adapter

Provider settings - e.g. github oauth app

  • Create oauth app on github at https://github.com/settings/developers
  • callback url - /api/auth/callback/github
  • Get client id and secret

next-auth handler endpoint

In .env.local file, store the secrets.
  • provider_client_id
  • provider_client_secret
  • NEXTAUTH_URL
  • auth_secret
  • jwt_secret
Then create api/auth/[...nextauth].js with below code.

import NextAuth from 'next-auth'
import AppleProvider from 'next-auth/providers/apple'
import GitHubProvider from 'next-auth/providers/github'
export default (req,res) => NextAuth(req,res, {
  providers : [
  GitHubProvider({
    clientId :  process.env.githubClientId,
    clientSecret : process.env.githubClientSecret
  })],
  debug : true,
  secret : "authsecrett",
  jwt : {
  secret : "mysehdihfu"
  }
})

Test that api handler is working

Just go to api/auth/signin and provider login buttons should be displayed.

Add SessionProvider in _app.tsx

You can use the context provider

import { Layout } from '../components/Layout'
import '../styles/globals.css'
import '../node_modules/highlight.js/styles/magula.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from "next-auth/react"
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
  return (
    <SessionProvider session={session} refetchInterval={5 * 60}>
      <Layout>
        
        <Component {...pageProps} />
      
      </Layout>
    </SessionProvider>
  )
}
export default MyApp

Use Session at client side


import { useSession, getSession } from "next-auth/react"
export default function Page() {
  const { data: session, status } = useSession()
  if (status === "loading") {
    return <p>Loading...</p>
  }
  if (status === "unauthenticated") {
    return <p>Access Denied</p>
  }
  return (
    <>
      <h1>Protected Page</h1>
      <p>You can view this page because you are signed in.</p>
    </>
  )
}
 

Get session data at client side


import { useSession } from 'next-auth/react'
const { data: session, status } = useSession()
if (status==="loading"){
  // return (<span>Loading</span>)
  return ("<span>Loading</span>")
 }
 //data: Session / undefined / null
 if (!session){
   return (<span>Please sign in first</span>)
 }
 if (session){
  return (<>{JSON.stringify(session)}</>)
}
 

Protecting api routes on server


import { getSession } from "next-auth/react"
export default async (req, res) => {
  const session = await getSession({ req })
  if (session) {
    // Signed in
    console.log("Session", JSON.stringify(session, null, 2))
  } else {
    // Not Signed in
    res.status(401)
  }
  res.end()
}
 

Getting token from server


import { getToken } from "next-auth/jwt"
const secret = process.env.SECRET
export default async (req, res) => {
const token = await getToken({ req, secret })
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}

Database adapter

So far we have not stored session or user information in database. So let's do it now. npm install @prisma/client @next-auth/prisma-adapter npm install prisma --save-dev
Complex problems, Simple Solutions