Aksha Malik
02/18/2026, 1:35 PMimport { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { environment } from '../../environments/environment';
@Component({
selector: 'app-auth-init',
standalone: true,
template: `<div style="text-align: center; margin-top: 50px;">Redirecting to secure signup...</div>`
})
export class AuthInitComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
async ngOnInit() {
const params = this.route.snapshot.queryParamMap;
const customerId = params.get('customer_id');
const email = params.get('email');
const firstName = params.get('first_name');
const lastName = params.get('last_name');
if (customerId) sessionStorage.setItem('signup_customer_id', customerId);
if (email) sessionStorage.setItem('signup_email', email);
if (firstName && lastName) sessionStorage.setItem('signup_name', `${firstName} ${lastName}`);
const codeVerifier = this.generateCodeVerifier();
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
const state = this.generateState();
sessionStorage.setItem('pkce_code_verifier', codeVerifier);
sessionStorage.setItem('pkce_state', state);
const signupAuthUrl = new URL(environment.descopeSignupAuthUrl);
const authUrl = new URL(signupAuthUrl);
authUrl.searchParams.append('client_id', environment.descopeProjectId);
authUrl.searchParams.append('redirect_uri', `${window.location.origin}/signup`);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'openid profile email');
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
authUrl.searchParams.append('state', state);
window.location.href = authUrl.toString();
}
private generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return this.base64UrlEncode(array);
}
private async generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return this.base64UrlEncode(new Uint8Array(hash));
}
private base64UrlEncode(array: Uint8Array): string {
return btoa(String.fromCharCode(...array)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
private generateState(): string {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return this.base64UrlEncode(array);
}
}
this is my auth url created: https://api.descope.com/oauth2/v1/authorize?client_id=P2uxgoL1Y0TWOdcHMn97uSLjRFYW&r[…]yIVbQ&code_challenge_method=S256&state=AENZaEbylLoc0rwCl8tM3gAkash Jivane
02/18/2026, 2:07 PMAksha Malik
02/18/2026, 2:12 PMAksha Malik
02/18/2026, 2:16 PMAksha Malik
02/18/2026, 2:16 PMAkash Jivane
02/18/2026, 2:19 PMAksha Malik
02/18/2026, 2:19 PMAkash Jivane
02/18/2026, 2:37 PMclient_id in the /authorize request?Aksha Malik
02/18/2026, 2:41 PMAkash Jivane
02/18/2026, 2:43 PMAksha Malik
02/18/2026, 2:44 PMAkash Jivane
02/18/2026, 2:51 PMclient_id). The intermittent issue is happening because the PKCE/OIDC state is getting reset or overwritten in the browser before the flow finishes.
This can happen in self-hosted BYOS if:
• the auth init runs more than once (Angular re-init)
sessionStorage values (verifier/state) get cleared/overwritten
• the flow restarts (e.g. due to debug=true)
When that happens, Descope can’t complete the OIDC transaction, so it redirects back to the flow without a code.Akash Jivane
02/18/2026, 2:53 PMpkce_code_verifier + state still exist when you return to /signup
3. Try localStorage instead of sessionStorage (to rule out storage reset)
4. Remove debug=true from the flow URL
Once the PKCE state stays consistent, you should always get the code on redirect.Aksha Malik
02/18/2026, 2:53 PMAksha Malik
02/18/2026, 3:12 PMpkce_code_verifier + state --> still exist after signup
3. using sessionStorage
4. removed debug=true
still it didn't work, please let me know if you want to connect over call so that this issue gets fixed soon.Akash Jivane
02/18/2026, 4:16 PMbyos-flow-copy, or screenshot of the last nodes / final steps in the flow ?
I think there might be a issue in the flow.
I will just quickly inspect it.Aksha Malik
02/18/2026, 4:27 PMAkash Jivane
02/18/2026, 4:29 PMAkash Jivane
02/18/2026, 4:50 PMBhagwanta Yeole
02/18/2026, 4:55 PMAksha Malik
02/18/2026, 4:59 PMBhagwanta Yeole
02/18/2026, 5:06 PMBhagwanta Yeole
02/18/2026, 5:06 PMAksha Malik
02/18/2026, 5:10 PMAksha Malik
02/19/2026, 4:17 AMBhagwanta Yeole
02/19/2026, 4:25 AMBhagwanta Yeole
02/19/2026, 4:25 AMBhagwanta Yeole
02/19/2026, 4:32 AMAksha Malik
02/19/2026, 6:39 AMBhagwanta Yeole
02/19/2026, 6:40 AMAksha Malik
02/19/2026, 6:41 AMBhagwanta Yeole
02/19/2026, 6:41 AMBhagwanta Yeole
02/19/2026, 6:45 AMAksha Malik
02/19/2026, 6:53 AMBhagwanta Yeole
02/19/2026, 6:53 AMBhagwanta Yeole
02/19/2026, 6:54 AMAmol Thite
02/19/2026, 8:47 AMstate and code_verifier during redirect handling. If the component re-initializes when returning from Descope, it can overwrite the original PKCE values and lead to intermittent authorization code inconsistencies.
As a workaround, I recommend adding a guard in your component logic to ensure PKCE values are generated only before the initial redirect to the /authorize endpoint, and not when handling the return from Descope (i.e., when the code parameter is already present in the URL). This helps prevent potential state/verifier mismatches and stabilizes the redirect flow.
If this is still not resolved I will raise this to our interval support team, and then they can help hereAksha Malik
02/19/2026, 9:05 AMAksha Malik
02/19/2026, 12:09 PMimport { Component, ChangeDetectorRef, AfterViewInit, NgZone, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { environment } from '../../environments/environment';
import { Router, ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-signup-flow',
standalone: true,
imports: [CommonModule, FormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './signup-flow.html',
styleUrls: ['./signup-flow.css']
})
export class SignupFlow implements OnInit, AfterViewInit {
public environment = environment;
private initInterval: any;
// Store URL params separately - don't use them to control initialization
private urlParams = { code: '', state: '' };
currentScreen = '';
showCustomScreen = false;
isDescopeInitialized = false; // Renamed for clarity
nextFn: ((interactionId: string, data?: any) => Promise<void>) | null = null;
errorText = '';
isLoading = false;
customerId: string | null = null;
form = { fullName: '', email: '', newPassword: '', confirmPassword: '' };
constructor(
private cdr: ChangeDetectorRef,
private ngZone: NgZone,
public router: Router,
private route: ActivatedRoute,
private http: HttpClient
) {}
ngOnInit() {
// Capture the code and state from URL, but don't let it affect Descope initialization
const code = this.route.snapshot.queryParamMap.get('code');
const state = this.route.snapshot.queryParamMap.get('state');
console.log("Code from URL:", code);
console.log("State from URL:", state);
if (code) {
this.urlParams = { code, state: state || '' };
}
// Always load session data for the form
this.customerId = sessionStorage.getItem('signup_customer_id');
this.form.fullName = sessionStorage.getItem('signup_name') || '';
this.form.email = sessionStorage.getItem('signup_email') || '';
}
ngAfterViewInit() {
// ALWAYS initialize Descope, regardless of whether code is present
this.initInterval = setInterval(() => {
const descopeElement = document.querySelector('descope-wc') as any;
if (descopeElement && !this.isDescopeInitialized) {
this.initializeDescope(descopeElement);
clearInterval(this.initInterval);
}
}, 50);
setTimeout(() => clearInterval(this.initInterval), 5000);
}
private initAttempts = 0;
initializeDescope(descopeElement: any) {
if (!descopeElement) return;
descopeElement.setAttribute('project-id', environment.descopeProjectId);
descopeElement.setAttribute('flow-id', environment.descopeFlowId);
descopeElement.onScreenUpdate = (screenName: string, state: any, next: any) => {
this.ngZone.run(() => {
// Handle empty screen name (Descope not ready)
if (!screenName || screenName === "") {
if (this.initAttempts < 5) {
this.initAttempts++;
console.warn(`Attempt ${this.initAttempts}: Screen name empty. Retrying...`);
setTimeout(() => {
// Just trigger a re-check without navigation
const elem = document.querySelector('descope-wc') as any;
if (elem && elem.refresh) {
elem.refresh();
}
}, 500);
return;
}
}
// Successfully got a screen name
this.currentScreen = screenName;
this.nextFn = next;
this.showCustomScreen = true;
this.isDescopeInitialized = true;
this.initAttempts = 0;
this.cdr.detectChanges();
});
return true;
};
}
async onSignupFormSubmit(event: Event) {
if (event) event.preventDefault();
if (!this.nextFn) {
this.errorText = 'Signup flow not ready. Please refresh the page.';
return;
}
this.isLoading = true;
this.errorText = '';
try {
const data = {
fullName: this.form.fullName,
email: this.form.email,
password: this.form.newPassword,
customAttributes: { customerId: this.customerId }
};
// Step 1: Complete the Descope signup
console.log('Submitting to Descope...');
const result: any = await this.nextFn(environment.signupFlowInteractionID, data);
console.log('Descope signup step successful:', result);
// Step 2: Exchange the code for tokens
if (this.urlParams.code && result && !result.error) {
console.log('Proceeding to backend exchange with code:', this.urlParams.code);
const token:any = await this.handleFinalExchange(this.urlParams.code, this.urlParams.state);
console.log("---------->token", token);
if (token) {
console.log('Signup and exchange successful, navigating to dashboard');
this.router.navigate(['/dashboard']);
}
} else {
throw new Error("No authorization code found. Please start the signup process again.");
}
} catch (error: any) {
console.error('Signup/Exchange Error:', error);
this.errorText = error.message || 'Signup failed';
this.isLoading = false;
this.cdr.detectChanges();
}
}
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');
}
return response;
}
ngOnDestroy() {
if (this.initInterval) {
clearInterval(this.initInterval);
}
}
}
above is the component it redirects to here we don't regenerate it tried to add guard but still it doesn't seem to work.Aksha Malik
02/19/2026, 12:16 PMBhagwanta Yeole
02/19/2026, 1:23 PMAksha Malik
02/19/2026, 1:23 PMBhagwanta Yeole
02/19/2026, 1:28 PMAksha Malik
02/19/2026, 1:28 PMAksha Malik
02/19/2026, 1:39 PMAksha Malik
02/19/2026, 1:40 PMBhagwanta Yeole
02/19/2026, 4:41 PMAksha Malik
02/19/2026, 4:42 PMBhagwanta Yeole
02/20/2026, 7:43 AMAksha Malik
02/20/2026, 7:45 AMAksha Malik
02/20/2026, 10:08 AMAksha Malik
02/20/2026, 2:16 PMAksha Malik
02/20/2026, 4:27 PMKevin Gao
02/21/2026, 4:12 PMAksha Malik
02/21/2026, 4:14 PMKevin Gao
02/22/2026, 2:44 PMKevin Gao
02/22/2026, 2:44 PMAksha Malik
02/22/2026, 3:11 PMAksha Malik
02/22/2026, 3:11 PMKevin Gao
02/23/2026, 6:00 PMAksha Malik
02/23/2026, 7:06 PM