How-To Add OneLogin Authentication To A Next.js App With NextAuth

March - 2022

I've got a Next.js app that I'm using OneLogin for the authentication on. The docs on OneLogin site talk about how to setup with React, but not Next.js specifically. Took me a bit to figure out the steps so I figured I'd write them up.

You'll want to get your OneLogin account stuff setup before going through this so your next.js app has something to talk to. Once you've got that setup, this is the process starting from scratch on a localhost environment (your milage may vary on windows)

Step 1

Create an app (e.g. named 'onelogin-app') with:

npx create-next-app@latest onelogin-app

Step 2

Move into the newly created onelogin-app app directory and install NextAuth with:

npm install next-auth

Step 3

Create a .env.local file and fill in these variables:

ONELOGIN_CLIENT_ID=YOUR_CLIENT_ID
ONELOGIN_CLIENT_SECRET=YOUR_CLIENT_SECRET
ONELOGIN_ISSUER=https://YOUR_SUBDOMAIN.onelogin.com
NEXTAUTH_URL=http://localhost:3000/
NEXTAUTH_SECRET=SOME_VERY_LONG_RANDOM_SECRET

Step 4

Make a directory named auth under pages/api so you end up with:

pages/api/auth

Step 5

Create a file named [...nextauth].js in the pages/api/auth you just created. So, the full path is:

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

Paste this into the file:

import NextAuth from 'next-auth'
import OneLoginProvider from 'next-auth/providers/onelogin'

export default NextAuth({
  providers: [
    OneLoginProvider({
      clientId: process.env.ONELOGIN_CLIENT_ID,
      clientSecret: process.env.ONELOGIN_CLIENT_SECRET,
      issuer: process.env.ONELOGIN_ISSUER,
    }),
  ],
})

Step 6

Add the next-auth stuff to the _app.js. Open:

pages/_app.js

delete the contents and replace them with this:

import '../styles/globals.css'
import { SessionProvider } from 'next-auth/react'

export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}

Step 7

This step shows how to check if the user is signed in on the front end. The check is done via the session variable. It shows if the user is singed in by showing their email address and a sign-out button if they are. If there is no user signed in, a sign-in button is displayed instead.

Open the file:

page/index.js

Delete the contents and replace them with this:

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

export default function Component() {
  const { data: session } = useSession()
  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>
    </>
  )
}

Step 8

This step adds the authentication check to an api endpoint.

Open the file:

pages/api/hello.js

Delete its contents and replace this with:

import { getSession } from 'next-auth/react'

export default async (req, res) => {
  const session = await getSession({ req })
  if (session) {
    res.status(200).json({ status: `signed in as: ${session.user.email}` })
  } else {
    res.status(401).json({ status: `not signed in` })
  }
}

Testing

That's it for the setup. You can test everything is working with:

npm run dev

Next Steps

[this needs editing...]

By default, the only stuff available in the session on the front end are:

{
  user: {
    name: 'some name',
    email: 'name@example.com',
    image: 'image_uri'
  },
  expires: '2022-04-17T03:32:54.523Z'
}

That really only tells you if someone is logged in or not and doesn't provide access to information from the profile that can be used to make display changes based off group access. (Note, this is only about display stuff and should not be considered secure for locking down access to things.)

To pass profile data based on the results of the sign in you have to pass from signIn -> jwt -> session.

TODO: write this up:

import NextAuth from 'next-auth'
import OneLoginProvider from 'next-auth/providers/onelogin'

export default NextAuth({
  providers: [
    OneLoginProvider({
      clientId: process.env.ONELOGIN_CLIENT_ID,
      clientSecret: process.env.ONELOGIN_CLIENT_SECRET,
      issuer: process.env.ONELOGIN_ISSUER,
      // this line is required to override the defaults and get groups
      authorization: { params: { scope: 'openid profile email groups' } },
    }),
  ],

  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      user.custom_data = 'HEREREHERE'
      return true
    },

    async session({ session, user, token }) {
      session.custom_data = token.custom_data
      return session
    },

    async jwt({ token, user, account, profile, isNewUser }) {
      if (user !== undefined) {
        token.custom_data = user.custom_data
      }
      return token
    },
  },
})

Also point out that you can use profile from jwt without having to start at signIn

TODO Check API stuff for what's there for dealing with request like that as well.