Built-in NextAuth Functions

NextAuth (or AuthJS) is an open source authentication library for web applications like NextJS, Svelte, Express, or SolidStart. As a native NextAuth Provider, Descope can be seamlessly integrated into your application whether you are already using NextAuth or adding it from scratch.

Be sure to check out our Getting Started guide for initializing a project from scratch here. This page simply covers the key functions relevant to implementing NextAuth with Descope.

Functions

Sign in

Signing in a user involves calling the sign in function from NextAuth and passing a provider name of descope in order for the Descope provider to be used. You can also pass a callback URL to redirect the user to after they've signed in. This will redirect the user to the Descope login page and then back to your application. The authentication flow that is redirected to can be found, and customized, in your Descope console, as elaborated on here.

sign-in.tsx
import { signIn } from "next-auth/react"
 
<button onClick={() => signIn("descope", { callbackUrl: "/dashboard" })}>Apply</button>

Sign out

For signing out a user, you'll have to sign out of both Descope and NextAuth. This can be done by calling the signOut function for NextAuth and then a federatedSignOut Server Action for Descope. The code could look something like this:

sign-out.tsx
'use client'
 
import { federatedSignOut } from "@/app/api/auth/federated-sign-out";
import { signOut } from "next-auth/react";
import { useRouter } from "next/navigation";
 
<button
    className="bg-red-500 text-white py-3 px-6 max-md:mt-6"
    onClick={async () => {
        try {
            await federatedSignOut().then(async () => {
                await signOut({ redirect: false });
                router.push("/");
            });
        } catch (error) {
        console.error("Error during sign out:", error);
        }
    }}
>
    Sign Out
</button>

The federatedSignOut Server Action is a custom function that you'll create in your NextJS application.

federated-sign-out.ts
'use server'
 
import { authOptions } from "@/app/_utils/options";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
 
export async function federatedSignOut() {
 
  // NEXTAUTH_URL is set in .env.local, defining the URL of the app
  const redirectUrl = process.env.NEXTAUTH_URL || '/'
 
  try {
    const session = await getServerSession(authOptions);
    if (!session) {
      redirect(redirectUrl);
    }
 
    const res = await fetch("https://api.descope.com/oauth2/v1/logout", {
      method: "POST",
      body: new URLSearchParams({
        // @ts-ignore
        id_token_hint: session.idToken,
        // Needed for OAuth logout endpoint
        post_logout_redirect_uri: redirectUrl,
      }),
    });
 
    if (res.status === 200) {
      return {
        message: "Successfully logged out of Descope",
      }
    } else {
      throw new Error("Failed to log out of Descope");
    }
  } catch (error) {
    throw new Error('Failed to log out of Descope')
  }
}

NextAuth options

auth-options.ts
import { NextAuthOptions } from "next-auth";
 
export const authOptions: NextAuthOptions = {
  providers: [
    {
      id: "descope",
      name: "Descope",
      type: "oauth",
      wellKnown: `https://api.descope.com/${process.env.DESCOPE_PROJECT_ID}/.well-known/openid-configuration`,
      authorization: {
        params: { scope: "openid email profile descope.custom_claims" },
      },
      idToken: true,
      clientId: process.env.DESCOPE_PROJECT_ID,
      clientSecret: process.env.DESCOPE_ACCESS_KEY,
      checks: ["pkce", "state"],
      profile(profile, tokens) {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          image: profile.picture,
          idToken: tokens.id_token,
          ...tokens,
        };
      },
    },
  ],
  secret: process.env.NEXTAUTH_SECRET
  callbacks: {
    async jwt({token, account, profile}) {
      if (account) {
        return {
          ...token,
          access_token: account.access_token,
          expires_at: Math.floor(Date.now() / 1000 + account.expires_in),
          refresh_token: account.refresh_token,
          profile: {
            name: profile?.name,
            email: profile?.email,
            image: profile?.picture,
            },
          }
        } else if (Date.now() < token.expires_at * 1000) {
          return token
        } else {
          try {
            const response = await fetch("https://api.descope.com/oauth2/v1/token", {
              headers: {"Content-Type": "application/x-www-form-urlencoded"},
              body: new URLSearchParams({
                client_id: "__ProjectID__",
                client_secret: "<Descope Access Key>",
                grant_type: "refresh_token",
                refresh_token: token.refresh_token,
              }),
              method: "POST",
            })
            
            const tokens = await response.json()
            
            if (!response.ok) throw tokens
            
            return {
              ...token,
              access_token: tokens.access_token,
              expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in),
              refresh_token: tokens.refresh_token ?? token.refresh_token,
            }
          } catch (error) {
            console.error("Error refreshing access token", error)
            return {...token, error: "RefreshAccessTokenError"}
            }
          }
        },
    
        async session({session, token}) {
          if (token.profile) {
            session.user = token.profile;
          }
    
          session.error = token.error
          session.accessToken = token.access_token
          return session
        },
    }
};

Customize

Now that you have the end-to-end application working with all of the built-in NextAuth functions, you can choose to configure and personalize many different areas of Descope, including your brand, style, custom user authentication journeys, etc. We recommend starting with customizing your user-facing screens, such as signup and login.

Was this helpful?

On this page