Aksha Malik
02/24/2026, 6:50 AMimport { Component, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { environment } from '../../environments/environment';
import { CustomWelcomeScreenComponent } from '../custom-welcome-screen/custom-welcome-screen.component';
import { DescopeAuthModule } from '@descope/angular-sdk';
@Component({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'app-byos-demo',
templateUrl: './test.html',
styleUrls: ['./test.scss'],
standalone: true,
imports: [CommonModule, CustomWelcomeScreenComponent, DescopeAuthModule]
})
export class ByosDemoComponent implements OnInit {
readonly flowId: string = environment.descopeFlowId || 'passwords-with-explicit-sign-up-byos';
state: {
error: { text: string; type: string } | null;
screenName?: string;
next?: (interactionId: string, form: Record<string, unknown>) => Promise<unknown>;
} = {
error: null,
screenName: undefined,
next: undefined
};
form: { name: string; email: string } = { name: '', email: '' };
constructor(
private router: Router,
private route: ActivatedRoute,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
// If Descope redirected back here with ?code=, forward to exchange-token
const code = this.route.snapshot.queryParamMap.get('code');
const state = this.route.snapshot.queryParamMap.get('state');
// if (code) {
// this.router.navigate(['/exchange-token'], {
// queryParams: { code, state }
// });
// }
}
onScreenUpdate = (
screenName: string,
context: Record<string, unknown>,
next: (interactionId: string, form: Record<string, unknown>) => Promise<unknown>
): boolean => {
this.state = {
...this.state,
...context,
screenName,
next
};
console.log('Screen update:', screenName, this.state);
this.cdr.detectChanges();
return screenName === 'Login';
};
onSuccess(event: CustomEvent): void {
console.log('Authentication successful:', event.detail);
// After BYOS flow completes successfully, navigate to exchange-token
// this.router.navigate(['/exchange-token']);
}
onError(event: CustomEvent): void {
console.error('Authentication error:', event);
}
updateForm(formData: { name: string; email: string }): void {
this.form = { ...formData };
}
async continueFlow(): Promise<void> {
try {
await this.state.next?.('XLmMGCy6IS', { ...this.form });
} catch (error) {
console.error('Error continuing flow:', error);
}
}
}
this is my byos html
<div class="byos-container">
<h2>Custom Authentication Example</h2>
<p>
This example demonstrates how to use custom screens with Descope
authentication
</p>
<div class="auth-container">
<descope
[flowId]="flowId"
[onScreenUpdate]="onScreenUpdate"
(success)="onSuccess($event)"
(error)="onError($event)"
>
<!--
This slot content is only shown when onScreenUpdate returns true.
Descope will pause the flow and wait until next() is called explicitly
via continueFlow(), which only happens on button click.
-->
<app-custom-welcome-screen
*ngIf="state.screenName === 'Create Account'"
[errorText]="state.error?.text || ''"
[prefillEmail]="form.email"
[prefillName]="form.name"
(formUpdate)="updateForm($event)"
(buttonClick)="continueFlow()"
></app-custom-welcome-screen>
</descope>
</div>
</div>
and created my custom screen as
import { Component, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-custom-welcome-screen',
templateUrl: './custom-welcome-screen.component.html',
styleUrls: ['./custom-welcome-screen.component.scss'],
standalone: true,
imports: [CommonModule, FormsModule]
})
export class CustomWelcomeScreenComponent implements OnChanges {
@Input() errorText: string = '';
@Input() prefillEmail: string = '';
@Input() prefillName: string = '';
@Output() formUpdate = new EventEmitter<{ name: string; email: string }>();
@Output() buttonClick = new EventEmitter<void>();
form = {
name: '',
email: ''
};
ngOnChanges(changes: SimpleChanges): void {
// Only prefill if the field is empty (don't overwrite user typing)
if (changes['prefillEmail'] && !this.form.email) {
this.form.email = this.prefillEmail;
}
if (changes['prefillName'] && !this.form.name) {
this.form.name = this.prefillName;
}
}
updateForm() {
this.formUpdate.emit({ ...this.form });
}
onSubmit(event: Event) {
event.preventDefault();
// Emit final form state before signaling button click
this.formUpdate.emit({ ...this.form });
this.buttonClick.emit();
}
}
i have also attached my flow below, how can i fix the issueAtharva Joshi
02/24/2026, 7:38 AMAksha Malik
02/24/2026, 7:39 AMAtharva Joshi
02/24/2026, 7:41 AMAksha Malik
02/24/2026, 7:41 AMAksha Malik
02/24/2026, 7:42 AMAksha Malik
02/24/2026, 7:42 AMAksha Malik
02/24/2026, 7:51 AMimport { Component, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { CustomWelcomeScreenComponent } from '../custom-welcome-screen/custom-welcome-screen.component';
import { DescopeAuthModule } from '@descope/angular-sdk';
import { firstValueFrom } from 'rxjs';
@Component({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'app-byos-demo',
templateUrl: './test.html',
styleUrls: ['./test.scss'],
standalone: true,
imports: [CommonModule, CustomWelcomeScreenComponent, DescopeAuthModule]
})
export class ByosDemoComponent implements OnInit {
readonly flowId: string = environment.descopeFlowId || 'passwords-with-explicit-sign-up-byos';
state: {
error: { text: string; type: string } | null;
screenName?: string;
next?: (interactionId: string, form: Record<string, unknown>) => Promise<unknown>;
} = {
error: null,
screenName: undefined,
next: undefined
};
form: { name: string; email: string } = { name: '', email: '' };
constructor(
private router: Router,
private route: ActivatedRoute,
private cdr: ChangeDetectorRef,
private http: HttpClient
) {}
ngOnInit(): void {
// If Descope redirected back here with ?code=, forward to exchange-token
const code = this.route.snapshot.queryParamMap.get('code');
const state = this.route.snapshot.queryParamMap.get('state');
sessionStorage.setItem('code', code ?? '');
sessionStorage.setItem('state', state ?? '');
// if (code) {
// this.router.navigate(['/exchange-token'], {
// queryParams: { code, state }
// });
// }
}
onScreenUpdate = (
screenName: string,
context: Record<string, unknown>,
next: (interactionId: string, form: Record<string, unknown>) => Promise<unknown>
): boolean => {
this.state = {
...this.state,
...context,
screenName,
next
};
console.log('Screen update:', screenName, this.state);
if (screenName === 'Login') {
// Automatically proceed to next step for Login screen
next('XLmMGCy6IS', { ...this.form }).then(() => {
console.log('Proceeded to next step');
});
// this.cdr.detectChanges();
// return false; // Return false to use Descope's default screen
}
if (screenName === 'Create account') {
this.cdr.detectChanges();
return true; // Return true to render custom screen
}
this.cdr.detectChanges();
return true;
};
onSuccess(event: CustomEvent): void {
console.log('Authentication successful:', event.detail);
}
onError(event: CustomEvent): void {
console.error('Authentication error:', event);
}
updateForm(formData: { name: string; email: string }): void {
this.form = { ...formData };
}
async continueFlow(): Promise<void> {
try {
console.log('Continuing flow with form data:', this.form);
const result = await this.state.next?.('AMeyg_7npF', { ...this.form });
console.log('Flow continued with result:', result);
if (result) {
// Handle successful flow continuation, e.g., navigate to dashboard
const finalExchangeResult = await this.handleFinalExchange(sessionStorage.getItem('code') || '', sessionStorage.getItem('state'));
if (finalExchangeResult) {
console.log('Final token exchange successful:', finalExchangeResult);
this.router.navigate(['/dashboard']);
} else {
console.error('Final token exchange failed');
}
}
} catch (error) {
console.error('Error continuing flow:', error);
}
}
async handleFinalExchange(code: string, state: string | null): Promise<any> {
const savedState = sessionStorage.getItem('pkce_state');
const codeVerifier = sessionStorage.getItem('pkce_code_verifier');
if (state && savedState && state !== savedState) {
throw new Error("Security state mismatch.");
}
if (!codeVerifier) {
throw new Error("Code verifier not found. Please start the signup process again.");
}
// Let the error bubble up to onSignupFormSubmit's catch block
return await this.exchangeCodeForToken(code, codeVerifier);
}
private async exchangeCodeForToken(code: string, codeVerifier: string): Promise<any> {
const response = await firstValueFrom(
<http://this.http.post|this.http.post><any>(`${environment.backendBaseUrlCPEGateway}/api/v1/auth/direct/exchange-code`, {
code,
codeVerifier
})
);
if (response.data.accessToken) {
localStorage.setItem('access_token', response.data.accessToken);
sessionStorage.removeItem('pkce_code_verifier');
sessionStorage.removeItem('pkce_state');
sessionStorage.removeItem('signup_customer_id');
sessionStorage.removeItem('signup_name');
sessionStorage.removeItem('signup_email');
sessionStorage.removeItem('code');
sessionStorage.removeItem('state');
}
return response;
}
}
can you please check if this is correct way of handling it
or please suggest me better way
@Atharva JoshiAtharva Joshi
02/24/2026, 7:59 AMAtharva Joshi
02/24/2026, 9:44 AMAkash Jivane
02/24/2026, 9:52 AMAkash Jivane
02/24/2026, 10:13 AMonScreenUpdate function and check if the works or not ?
onScreenUpdate = async (screenName, context, next) => {
this.state = {
...this.state,
...context,
screenName,
next,
interactionId: context?.interactionId
};
// Only act on first screen
if (screenName === 'Login') {
const email = this.form?.email || context?.user?.loginId;
const userExists = await this.checkIfUserExists(email);
if (userExists) {
const loginInteraction = context.interactions.find(i =>
i.text.toLowerCase().includes('sign in') ||
i.text.toLowerCase().includes('login')
);
setTimeout(() => {
next(loginInteraction.interactionId, { loginId: email });
}, 0);
return false;
}
return false;
}
if (screenName === 'Create account') {
return true;
}
return false;
};Aksha Malik
02/24/2026, 10:14 AMAksha Malik
02/24/2026, 10:22 AMAngular is running in development mode.
descope-angular-sdk.mjs:1554 DescopeAuthModule is loaded in a child module
2index.js:2 [Descope] project-id cannot be empty, please make sure to set this attribute
jo.reduce.t.<computed> @ index.js:2Understand this error
index.js:2 [Descope] Step "Login" is waiting
{
"screen": {
"id": "passwords-with-explicit-sign-up-byos/SC3A6XXWjuju9VdtBn7Wf126bSh9T",
"state": {
"componentsConfig": {
"samlGroupMappings": {},
"ssoApplications": {},
"thirdPartyAppApproveScopes": {},
"userRoles": {},
"userSelectedTenant": {}
},
"form": {
"spAcsUrl": "",
"spEntityId": "",
"spMetadataUrl": ""
},
"inputs": {
"spAcsUrl": "",
"spEntityId": "",
"spMetadataUrl": ""
},
"project": {
"name": "POC"
}
}
},
"status": "waiting",
"stepId": "1",
"stepName": "Login",
"action": "",
"error": null
}
index.js:1 ERROR TypeError: Cannot read properties of undefined (reading 'find')
at _SignupByosComponent.<anonymous> (signup-byos.component.ts:121:51)
at Generator.next (<anonymous>)
at main.js:18:61
at new ZoneAwarePromise (zone.js:2701:25)
at __async (main.js:2:10)
at Es.onScreenUpdate (signup-byos.component.ts:111:74)
at Es.<anonymous> (index.js:2:94924)
at Generator.next (<anonymous>)
at s (index.js:1:653)Aksha Malik
02/24/2026, 10:22 AMAkash Jivane
02/24/2026, 10:27 AMAkash Jivane
02/24/2026, 12:26 PMAksha Malik
02/24/2026, 12:27 PMAkash Jivane
02/24/2026, 12:29 PMAksha Malik
02/24/2026, 12:30 PMAksha Malik
02/24/2026, 2:35 PMAkash Jivane
02/24/2026, 2:45 PMAkash Jivane
02/24/2026, 3:00 PMonScreenUpdate function -
onScreenUpdate = (
screenName: string,
context: Record<string, unknown>,
next: (interactionId: string, form: Record<string, unknown>) => Promise<unknown>
): boolean => {
this.state = {
...this.state,
...context,
screenName,
next
};
console.log('Screen update:', screenName, this.state);
if (screenName === 'Login') {
next('XLmMGCy6IS', { ...this.form }); // existing login interaction
return false;
}
if (screenName === 'Forgot password') {
next('FORGOT_PASSWORD_INTERACTION_ID', {
loginId: this.form.email
});
return false;
}
if (screenName === 'Reset password') {
this.cdr.detectChanges();
return true;
}
if (screenName === 'Create account') {
this.cdr.detectChanges();
return true;
}
return true;
};Aksha Malik
02/24/2026, 3:01 PMAkash Jivane
02/24/2026, 3:02 PMasync submitNewPassword(newPassword: string): Promise<void> {
try {
const result = await this.state.next?.(
'RESET_PASSWORD_INTERACTION_ID',
{ password: newPassword }
);
console.log('Password reset success:', result);
if (result) {
const exchange = await this.handleFinalExchange(
sessionStorage.getItem('code') || '',
sessionStorage.getItem('state')
);
if (exchange) {
this.router.navigate(['/dashboard']);
}
}
} catch (error) {
console.error('Reset password failed:', error);
}
}Akash Jivane
02/24/2026, 3:02 PMAksha Malik
02/24/2026, 3:20 PMAkash Jivane
02/24/2026, 3:29 PMAksha Malik
02/24/2026, 5:32 PMAkash Jivane
02/25/2026, 8:33 AM