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.
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:
'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.
'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
import { NextAuthOptions } from "next-auth";
export const authOptions: NextAuthOptions = {
providers: [
{
id: "descope",
name: "Descope",
type: "oauth",
wellKnown: `https://api.descope.com/${process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID}/.well-known/openid-configuration`,
authorization: {
params: { scope: "openid email profile descope.custom_claims" },
},
idToken: true,
clientId: process.env.NEXT_PUBLIC_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.