nOTP (WhatsApp) Authentication with Client SDKs
This guide is meant for developers that are NOT using Descope Flows to design login screens and authentication methods.
If you would like to use Descope Flows, Quick Start should be your starting point.
nOTP allows users to log in via WhatsApp with just a single click, eliminating the need for codes, usernames, and typing. Unlike traditional OTP methods, nOTP doesn't require the company to connect to email servers or SMS providers, which can significantly reduce costs as it scales with the number of users.
To get started with authentication using nOTP (WhatsApp), refer to our nOTP Documentation. Continue reading to learn how to integrate nOTP authentication into your application using our Client SDKs.
The notp methods are exposed by the core Descope JavaScript SDK and are available through the Descope client SDKs: React, Next.js, Vue, Angular, WebJS, and plain HTML via the WebJS UMD bundle. The main differences are how you obtain the SDK instance, and that the Angular SDK wraps promise-returning SDK methods, including notp methods, as RxJS Observables, so Angular examples use .subscribe() instead of await.
Client SDK
Install SDK
npm i --save @descope/react-sdknpm i --save @descope/nextjs-sdknpm i --save @descope/web-js-sdknpm i --save @descope/vue-sdknpm i --save @descope/angular-sdkImport and initialize SDK
For more information about the baseUrl, baseStaticUrl, and baseCdnUrl parameters, refer to the Base URL Configuration section.
Parameters:
baseUrl: Custom domain that must be configured to manage token response in cookies. This makes sure every request to our service is through your custom domain, preventing accidental domain blockages.baseStaticUrl: Custom domain to override the base URL that is used to fetch static files.baseCdnUrl: Custom domain to override the base URL used to load external script assets (e.g., SDKs or widgets) dynamically at runtime.persistTokens: Controls whether session tokens are stored in browser localStorage. Enabled by default and accessible viagetToken(). Set tofalseto avoid client-side storage of tokens to reduce XSS risk.autoRefresh: Controls whether the session is automatically refreshed when the token is expired. Enabled by default. Set tofalseto disable automatic refresh of the session.sessionTokenViaCookie: Controls whether the session token is stored in a cookie instead of localStorage. IfpersistTokensis true, then by default, the token is stored in localStorage. Set this totrueto store the token in a JS cookie instead.storeLastAuthenticatedUser: Determines if the last authenticated user's info is saved in localStorage. Enabled by default and accessible viagetUser(). Set tofalseto disable this behavior.keepLastAuthenticatedUserAfterLogout: Controls whether user info is kept after logout. Disabled by default. Set totrueto store user data on logout.
import { AuthProvider } from '@descope/react-sdk'
import { Descope, useDescope } from '@descope/react-sdk'
const AppRoot = () => {
return (
<AuthProvider
projectId="__ProjectID__"
baseUrl="https://auth.app.example.com"
baseCdnUrl="https://assets.app.example.com" // specify a custom CDN URL for fetching external scripts and resources
persistTokens={true} // set to `false` to disable token storage in browser to prevent XSS
autoRefresh={true} // set to `false` to disable automatic refresh of the session
sessionTokenViaCookie={false} // set to `true` to store the session token in a JS cookie instead of localStorage
storeLastAuthenticatedUser={true} // set to `false` to disable storing last user
keepLastAuthenticatedUserAfterLogout={false} // set to `true` to persist user info after logout
>
<App />
</AuthProvider>
);
};import { AuthProvider } from '@descope/nextjs-sdk';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<AuthProvider
projectId="__ProjectID__"
baseUrl="<URL>"
baseCdnUrl="https://assets.app.example.com" // specify a custom CDN URL for fetching external scripts and resources
persistTokens={true} // set to `false` to disable token storage in browser to prevent XSS
autoRefresh={true} // set to `false` to disable automatic refresh of the session
sessionTokenViaCookie={false} // set to `true` to store the session token in a JS cookie instead of localStorage
storeLastAuthenticatedUser={true} // set to `false` to disable storing last user
keepLastAuthenticatedUserAfterLogout={false} // set to `true` to persist user info after logout
>
<html lang="en">
<body>{children}</body>
</html>
</AuthProvider>
);
}import DescopeSdk from '@descope/web-js-sdk';
try {
const descopeSdk = DescopeSdk({
projectId: '__ProjectID__',
baseUrl: 'https://auth.app.example.com',
baseCdnUrl="https://assets.app.example.com", // specify a custom CDN URL for fetching external scripts and resources
persistTokens: true, // set to `false` to disable token storage in browser to prevent XSS
autoRefresh: true, // set to `false` to disable automatic refresh of the session
sessionTokenViaCookie: false, // set to `true` to store the session token in a JS cookie instead of localStorage
storeLastAuthenticatedUser: true, // set to `false` to disable storing last user
keepLastAuthenticatedUserAfterLogout: false, // set to `true` to persist user info after logout
});
} 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: '__ProjectID__',
baseUrl: "<base url>",
baseCdnUrl: "https://assets.app.example.com", // specify a custom CDN URL for fetching external scripts and resources
persistTokens: true, // set to `false` to disable token storage in browser to prevent XSS
autoRefresh: true, // set to `false` to disable automatic refresh of the session
sessionTokenViaCookie: false, // set to `true` to store the session token in a JS cookie instead of localStorage
storeLastAuthenticatedUser: true, // set to `false` to disable storing last user
keepLastAuthenticatedUserAfterLogout: false, // set to `true` to persist user info after logout
});
const sdk = getSdk();
sdk?.onSessionTokenChange((newSession) => {
// here you can implement custom logic when the session is changing
});// 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',
baseUrl: '<URL>',
baseCdnUrl: "https://assets.app.example.com", // specify a custom CDN URL for fetching external scripts and resources
persistTokens: true, // set to `false` to disable token storage in browser to prevent XSS
autoRefresh: true, // set to `false` to disable automatic refresh of the session
sessionTokenViaCookie: false, // set to `true` to store the session token in a JS cookie instead of localStorage
storeLastAuthenticatedUser: true, // set to `false` to disable storing last user
keepLastAuthenticatedUserAfterLogout: false // set to `true` to persist user info after logout
})
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [DescopeAuthService],
multi: true
},
provideHttpClient(withInterceptors([descopeInterceptor]))
],
bootstrap: [AppComponent]
})
export class AppModule { }OIDC Configuration
If you're using our SDK as an OIDC client with our Federated Apps, you can initialize the oidcConfig parameter with the following items:
applicationId: This is the application id, that can be found within the settings of your Federated ApplicationredirectUri: This is the url that will be redirected to if the user is unauthenticated. The default redirect URI will be used if not provided.scope: This is a string of the scopes that the OIDC client will request from Descope. This should be one string value with spaces in between each scope. The default scopes are:'openid email roles descope.custom_claims offline_access'
User Sign-Up
To implement nOTP (WhatsApp) authentication, the first step is user sign-up using the nOTP (WhatsApp) authentication method. Use the signUp function to create a new user via WhatsApp. The Login ID should ideally be a phone number or can be left empty, in which case the phone number from WhatsApp will be used as the Login ID during verification. After calling the sign-up function, you will receive a response that includes a redirect URL or a QR code image. Present this to the user, who will then use WhatsApp to scan the QR code or follow the link to begin the authentication process.
import { useDescope } from '@descope/react-sdk';
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize NOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP signup.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete auth
console.log(resp.data)
}'use client';
import { useDescope } from '@descope/nextjs-sdk/client';
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize NOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP signup.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete auth
console.log(resp.data)
}// Inside your component's <script setup>
import { useDescope } from '@descope/vue-sdk';
const sdk = useDescope();
async function notpSignUp() {
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
const resp = await sdk.notp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize NOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP signup.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete auth
console.log(resp.data)
}
}// Inject DescopeAuthService in your component:
// constructor(private authService: DescopeAuthService) {}
// Note: In the Angular SDK, auth state is exposed through RxJS Observables
// like session$ and user$. SDK calls used from DescopeAuthService/descopeSdk
// should be handled with subscribe(...).
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
this.authService.descopeSdk.notp.signUp(loginId, user).subscribe((resp) => {
if (!resp.ok) {
console.log("Failed to initialize NOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP signup.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete auth
console.log(resp.data)
}
});// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
const resp = await descopeSdk.notp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize NOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP signup.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete auth
console.log(resp.data)
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function notpSignUp() {
// Args:
// loginId: phone - becomes the loginId for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = document.getElementById("phone").value
// user: Optional user object to populate new user information.
const user = { "name": document.getElementById("name").value, "phone": document.getElementById("phone").value, "email": document.getElementById("email").value}
const resp = await descopeSdk.notp.signUp(loginId, user);
if (!resp.ok) {
window.alert("Failed to initialize signup flow\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized signup flow")
// resp.data contains { pendingRef, redirectUrl, image }
// Render the QR `image`, then poll waitForSession(resp.data.pendingRef) to complete auth
window.location.replace("./notpDisplay?image=" + encodeURIComponent(resp.data.image) + "&redirectUrl=" + encodeURIComponent(resp.data.redirectUrl) + "&pendingRef=" + encodeURIComponent(resp.data.pendingRef))
}
}
</script>User Sign-In
To sign in a user with nOTP (WhatsApp) authentication, use the signIn function with the nOTP (WhatsApp) authentication method. The Login ID should be a phone number or can be left empty. If left empty, the phone number from WhatsApp will be used as the login ID during the verification process. Upon calling the sign-in function, the response will include a redirect URL and/or a QR code image. Present this information to the user; they should scan the QR code or follow the link in WhatsApp to start the authentication flow.
import { useDescope } from '@descope/react-sdk';
// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signIn(loginId, loginOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete sign-in
console.log(resp.data)
}'use client';
import { useDescope } from '@descope/nextjs-sdk/client';
// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signIn(loginId, loginOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete sign-in
console.log(resp.data)
}// Inside your component's <script setup>
import { useDescope } from '@descope/vue-sdk';
const sdk = useDescope();
async function notpSignIn() {
// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await sdk.notp.signIn(loginId, loginOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete sign-in
console.log(resp.data)
}
}// Inject DescopeAuthService in your component:
// constructor(private authService: DescopeAuthService) {}
// Note: In the Angular SDK, auth state is exposed through RxJS Observables
// like session$ and user$. SDK calls used from DescopeAuthService/descopeSdk
// should be handled with subscribe(...).
// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
this.authService.descopeSdk.notp.signIn(loginId, loginOptions).subscribe((resp) => {
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete sign-in
console.log(resp.data)
}
});// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await descopeSdk.notp.signIn(loginId, loginOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete sign-in
console.log(resp.data)
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function notpSignIn() {
// Args:
// loginId: phone - must be same as provided at the time of signup (or leave empty to use the WhatsApp phone number).
const loginId = document.getElementById("phone").value
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await descopeSdk.notp.signIn(loginId, loginOptions);
if (!resp.ok) {
window.alert("Failed to initialize NOTP Sign-In\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-In")
// resp.data contains { pendingRef, redirectUrl, image }
// Render the QR `image`, then poll waitForSession(resp.data.pendingRef) to complete sign-in
window.location.replace("./notpDisplay?image=" + encodeURIComponent(resp.data.image) + "&redirectUrl=" + encodeURIComponent(resp.data.redirectUrl) + "&pendingRef=" + encodeURIComponent(resp.data.pendingRef))
}
}
</script>User Sign-Up-or-In
Use the signUpOrIn function to authenticate a user with nOTP (WhatsApp). If the user does not exist, signUpOrIn will create a new user automatically. The Login ID should be a phone number or can be left empty. If left empty, the WhatsApp phone number will be used as the login ID during verification. After calling signUpOrIn, the response will contain a redirect URL and/or a QR code image. Present the QR code or URL to the user, who should scan the QR code or follow the link in WhatsApp to start the authentication flow.
import { useDescope } from '@descope/react-sdk';
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signUpOrIn(loginId, signUpOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-Up or Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete authentication
console.log(resp.data)
}'use client';
import { useDescope } from '@descope/nextjs-sdk/client';
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const descopeSdk = useDescope();
const resp = await descopeSdk.notp.signUpOrIn(loginId, signUpOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-Up or Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete authentication
console.log(resp.data)
}// Inside your component's <script setup>
import { useDescope } from '@descope/vue-sdk';
const sdk = useDescope();
async function notpSignUpOrIn() {
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await sdk.notp.signUpOrIn(loginId, signUpOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-Up or Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete authentication
console.log(resp.data)
}
}// Inject DescopeAuthService in your component:
// constructor(private authService: DescopeAuthService) {}
// Note: In the Angular SDK, auth state is exposed through RxJS Observables
// like session$ and user$. SDK calls used from DescopeAuthService/descopeSdk
// should be handled with subscribe(...).
// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
this.authService.descopeSdk.notp.signUpOrIn(loginId, signUpOptions).subscribe((resp) => {
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-Up or Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete authentication
console.log(resp.data)
}
});// Args:
// loginId: phone - becomes the unique ID for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = "+15555555555"
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await descopeSdk.notp.signUpOrIn(loginId, signUpOptions);
if (!resp.ok) {
console.log("Failed to initialize NOTP Sign-Up or Sign-In")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In.")
// resp.data contains { pendingRef, redirectUrl, image }
// Present the QR `image` or send the user to `redirectUrl` to complete authentication
console.log(resp.data)
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function notpSignUpOrIn() {
// Args:
// loginId: phone - becomes the loginId for the user from here on (or leave empty to use the WhatsApp phone number).
const loginId = document.getElementById("phone").value
// signUpOptions (SignUpOptions): this allows you to configure behavior during the authentication process.
const signUpOptions = {
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
const resp = await descopeSdk.notp.signUpOrIn(loginId, signUpOptions);
if (!resp.ok) {
window.alert("Failed to initialize NOTP Sign-Up or Sign-In\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized NOTP Sign-Up or Sign-In")
// resp.data contains { pendingRef, redirectUrl, image }
// Render the QR `image`, then poll waitForSession(resp.data.pendingRef) to complete authentication
window.location.replace("./notpDisplay?image=" + encodeURIComponent(resp.data.image) + "&redirectUrl=" + encodeURIComponent(resp.data.redirectUrl) + "&pendingRef=" + encodeURIComponent(resp.data.pendingRef))
}
}
</script>Get Session
To complete the WhatsApp nOTP flow, after the user completes verification in WhatsApp, retrieve their JWT by invoking the waitForSession function and passing in the pendingRef from your signIn / signUp / signUpOrIn call.
The function polls Descope until the user finishes verifying in WhatsApp, then returns the session and refresh tokens (and, with persistTokens enabled, persists them for you). By default, polling runs every 1 second and times out after 10 minutes.
import { useDescope } from '@descope/react-sdk';
// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
const pendingRef = resp.data.pendingRef
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
const descopeSdk = useDescope();
const sessionResp = await descopeSdk.notp.waitForSession(pendingRef, config);
if (!sessionResp.ok) {
// Note: a timeout does NOT throw - it resolves with ok: false, so check it here.
console.log("Failed to complete NOTP authentication")
console.log("Status Code: " + sessionResp.code)
console.log("Error Code: " + sessionResp.error.errorCode)
console.log("Error Description: " + sessionResp.error.errorDescription)
console.log("Error Message: " + sessionResp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP. " + JSON.stringify(sessionResp.data))
// sessionResp.data is a JWTResponse containing sessionJwt, refreshJwt, etc.
}'use client';
import { useDescope } from '@descope/nextjs-sdk/client';
// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
const pendingRef = resp.data.pendingRef
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
const descopeSdk = useDescope();
const sessionResp = await descopeSdk.notp.waitForSession(pendingRef, config);
if (!sessionResp.ok) {
// Note: a timeout does NOT throw - it resolves with ok: false, so check it here.
console.log("Failed to complete NOTP authentication")
console.log("Status Code: " + sessionResp.code)
console.log("Error Code: " + sessionResp.error.errorCode)
console.log("Error Description: " + sessionResp.error.errorDescription)
console.log("Error Message: " + sessionResp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP. " + JSON.stringify(sessionResp.data))
// sessionResp.data is a JWTResponse containing sessionJwt, refreshJwt, etc.
}// Inside your component's <script setup>
import { useDescope } from '@descope/vue-sdk';
const sdk = useDescope();
async function notpWaitForSession(pendingRef) {
// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
const resp = await sdk.notp.waitForSession(pendingRef, config);
if (!resp.ok) {
// Note: a timeout does NOT throw - it resolves with ok: false, so check it here.
console.log("Failed to complete NOTP authentication")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP. " + JSON.stringify(resp.data))
// resp.data is a JWTResponse containing sessionJwt, refreshJwt, etc.
}
}// Inject DescopeAuthService in your component:
// constructor(private authService: DescopeAuthService) {}
// Note: In the Angular SDK, auth state is exposed through RxJS Observables
// like session$ and user$. SDK calls used from DescopeAuthService/descopeSdk
// should be handled with subscribe(...).
// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
const pendingRef = "<pending-ref-from-sign-in>"
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
this.authService.descopeSdk.notp.waitForSession(pendingRef, config).subscribe((resp) => {
if (!resp.ok) {
// Note: a timeout does NOT error - it emits ok: false, so check it here.
console.log("Failed to complete NOTP authentication")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP. " + JSON.stringify(resp.data))
// resp.data is a JWTResponse containing sessionJwt, refreshJwt, etc.
}
});// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
const pendingRef = resp.data.pendingRef
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
const sessionResp = await descopeSdk.notp.waitForSession(pendingRef, config);
if (!sessionResp.ok) {
// Note: a timeout does NOT throw - it resolves with ok: false, so check it here.
console.log("Failed to complete NOTP authentication")
console.log("Status Code: " + sessionResp.code)
console.log("Error Code: " + sessionResp.error.errorCode)
console.log("Error Description: " + sessionResp.error.errorDescription)
console.log("Error Message: " + sessionResp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP. " + JSON.stringify(sessionResp.data))
// sessionResp.data is a JWTResponse containing sessionJwt, refreshJwt, etc.
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function notpWaitForSession(thisPendingRef) {
// Args:
// pendingRef: the reference string returned from notp.signIn / signUp / signUpOrIn.
const pendingRef = thisPendingRef
// config (optional WaitForSessionConfig): tune how long and how often to poll.
const config = {
"timeoutMs": 120000, // give up after 2 minutes (default applies if omitted)
"pollingIntervalMs": 1000 // poll once per second (default applies if omitted)
}
const resp = await descopeSdk.notp.waitForSession(pendingRef, config);
if (!resp.ok) {
// Note: a timeout does NOT throw - it resolves with ok: false, so check it here.
window.alert("Failed to complete NOTP authentication\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully authenticated via NOTP")
window.location.replace("../loggedIn.html?sessionJwt=" + encodeURIComponent(resp.data.sessionJwt))
}
}
</script>Checkpoint
Your application is now integrated with Descope. Please test with sign-up or sign-in use case.