Descope Components

Introduction

Descope Components are components used to render Descope Flows and provide context in your application. This includes the UI elements and logic as defined in your project console.

Install SDK

ReactNextJSNextAuthWebJSVue.jsHTMLAngular
npm i --save @descope/react-sdk
npm i --save @descope/next-sdk
npm i --save next-auth
npm i --save @descope/web-js-sdk
npm i --save @descope/vue-sdk
<!-- Not applicable for HTML -->
npm i --save @descope/angular-sdk

Import and initialize SDK

ReactNextJSNextAuthWebJSVue.jsHTMLAngular
import { AuthProvider } from '@descope/react-sdk'
import { Descope, useDescope } from '@descope/react-sdk'

const AppRoot = () => {
	return (
		<AuthProvider
			projectId="__ProjectID__"
			// If the Descope project manages the token response in cookies,
            // a custom domain must be configured
            // (e.g., https://auth.app.example.com)
			// and should be set as the baseUrl property.
			// baseUrl = "https://auth.app.example.com"
		>
			<App />
		</AuthProvider>
	);
};
import { AuthProvider } from '@descope/nextjs-sdk';

export default function RootLayout({ children }: { children: React.ReactNode }) {
	return (
		<AuthProvider projectId="__ProjectID__">
			<html lang="en">
				<body>{children}</body>
			</html>
		</AuthProvider>
	);
}
// app/api/auth/[...nextauth]
import { NextAuthOptions } from "next-auth"
import NextAuth from "next-auth/next";

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" } },
      idToken: true,
      clientId: process.env.DESCOPE_PROJECT_ID,
      clientSecret: process.env.DESCOPE_ACCESS_KEY,
      checks: ["pkce", "state"],
      profile(profile) {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          image: profile.picture,
        }
      },
    },
  ], 
  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
        },
    }
}

const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

// app/provider.tsx
'use client'

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


export default function NextAuthSessionProvider(
    { children }:
    { children: React.ReactNode }
) {
    return (
        <SessionProvider>
            { children }
        </SessionProvider>
    )
}

// app/layout.tsx
import NextAuthSessionProvider from './provider'


export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <NextAuthSessionProvider>
          <div>
            {children}
          </div>
        </NextAuthSessionProvider>
      </body>
    </html>
  )
}
import DescopeSdk from '@descope/web-js-sdk';
try {
    //  baseUrl="<URL>" // you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize CNAME within your Descope project.
    const descopeSdk = DescopeSdk({ projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
} catch (error) {
    // handle the error
    console.log("failed to initialize: " + error)
}
import { createApp } from "vue";
import App from "@/App.vue";
import descope, { getSdk } from "@descope/vue-sdk";

const app = createApp(App);
app.use(router);

app.use(descope, {
  projectId: '__Project_ID__',
  baseUrl: "<base url>", // Optional
});

const sdk = getSdk();
sdk?.onSessionTokenChange((newSession) => {
  // here you can implement custom logic when the session is changing
});
<head>
    <script src="https://unpkg.com/@descope/web-js-sdk@x.x.x/dist/index.umd.js"></script>
    <!-- Please replace `x.x.x` with the latest version of the WebJS SDK, which you can get from [here](https://www.npmjs.com/package/@descope/web-js-sdk) -->
</head>
// app.module.ts
import { NgModule, APP_INITIALIZER} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DescopeAuthModule, DescopeAuthService, descopeInterceptor } from '@descope/angular-sdk';
import { AppComponent } from './app.component';
import {
	HttpClientModule,
	provideHttpClient,
	withInterceptors
} from '@angular/common/http';
import { zip } from 'rxjs';

export function initializeApp(authService: DescopeAuthService) {
	return () => zip([authService.refreshSession(), authService.refreshUser()]);
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    DescopeAuthModule.forRoot({
      projectId: 'YOUR_PROJECT_ID'
    })
  ],
  providers: [
    {
        provide: APP_INITIALIZER,
        useFactory: initializeApp,
        deps: [DescopeAuthService],
        multi: true
      },
      provideHttpClient(withInterceptors([descopeInterceptor]))
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Customization

Descope Component

Customize the Descope Component by passing in the following props:

import { Descope } from '@descope/react-sdk'

const App = () => {
    return (
        {...}
        <Descope
            flowId="my-flow-id"
            onSuccess={(e) => console.log('Logged in!')}
            onError={(e) => console.log('Could not logged in')}
            // theme can be "light", "dark" or "os", which auto select a theme based on the OS theme. Default is "light"
            // theme="dark"

            // debug can be set to true to enable debug mode
            // debug={true}

            // tenant ID for SSO (SAML) login. If not provided, Descope will use the domain of available email to choose the tenant
            // tenant=<tenantId>

            // Redirect URL for OAuth and SSO (will be used when redirecting back from the OAuth provider / IdP), or for "Magic Link" and "Enchanted Link" (will be used as a link in the message sent to the the user)
            // redirectUrl=<redirectUrl>

            // autoFocus can be true, false or "skipFirstScreen". Default is true.
            // - true: automatically focus on the first input of each screen
            // - false: do not automatically focus on screen's inputs
            // - "skipFirstScreen": automatically focus on the first input of each screen, except first screen
            // autoFocus="skipFirstScreen"

            // errorTransformer is a function that receives an error object and returns a string. The returned string will be displayed to the user.
            // NOTE: errorTransformer is not required. If not provided, the error object will be displayed as is.
            // Example:
            // const errorTransformer = useCallback(
            // 	(error: { text: string; type: string }) => {
            // 		const translationMap = {
            // 			SAMLStartFailed: 'Failed to start SAML flow'
            // 		};
            // 		return translationMap[error.type] || error.text;
            // 	},
            // 	[]
            // );
            // ...
            // errorTransformer={errorTransformer}
            // ...
        />
    )
}

Default Flows

Use default flow components that render the Descope component with a predefined flow ID.

import { SignInFlow } from '@descope/react-sdk'
// you can choose flow to run from the following
// import { SignUpFlow } from '@descope/react-sdk'
// import { SignUpOrInFlow } from '@descope/react-sdk'

const App = () => {
    return (
        {...}
        <SignInFlow
            onSuccess={(e) => console.log('Logged in!')}
            onError={(e) => console.log('Could not logged in!')}
        />
    )
}

Auth Provider

Passing sessionTokenViaCookie prop to AuthProvider component. Descope SDK will automatically store session token on the DS cookie.

Note: Use this option if session token will stay small (less than 1k). Session token can grow, especially in cases of using authorization, or adding custom claims

import { AuthProvider } from '@descope/react-sdk';

const AppRoot = () => {
	return (
		<AuthProvider
     projectId="my-project-id"
     sessionTokenViaCookie
     // If the Descope project manages the token response in cookies, a custom domain
     // must be configured (e.g., https://auth.app.example.com)
     // and should be set as the baseUrl property.
     // baseUrl = "https://auth.app.example.com"
    >
			<App />
		</AuthProvider>
	);
};

Now, whenever you call fetch, the cookie will automatically be sent with the request. Descope backend SDKs also support extracting the token from the DS cookie.

Note: The session token cookie is set as a Secure cookie. It will be sent only over HTTPS connections. In addition, some browsers (e.g. Safari) may not store Secure cookie if the hosted page is running on an HTTP protocol.

Widgets

Widgets are Descope components that let you delegate user operations to your customers. Whether the implementation is tenant-based or not, The Descoper can embed those components inside his website to allow his customers to perform self-service in various operations. Here is an example of the UserManagement widget implementation, using the react SDK:
import { UserManagement } from '@descope/react-sdk';
...
  <UserManagement
    widgetId="user-management-widget"
    tenant="tenant-id"
  />

You can read on further Widget types and implementation methods here.