Aksha Malik
04/08/2026, 2:03 PM<div style="text-align: center; margin-top: 50px; font-family: sans-serif;">
<h2>Redirecting to secure login...</h2>
<div class="loader"></div>
</div>,
styles: [.loader { border: 4px solid #F3F3F3; border-top: 4px solid #3498DB; border-radius: 50%; width: 30px; height: 30px; animation: spin 2s linear infinite; margin: 20px auto; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }]
})
export class GatewayComponent implements OnInit {
constructor(private route: ActivatedRoute, private router: Router) {}
async ngOnInit() {
const params = this.route.snapshot.queryParamMap;
// 2. Identify Flow Intent (The Initial Landing)
const customerId = params.get('customer_id');
const email = params.get('email');
const firstName = params.get('first_name');
const lastName = params.get('last_name');
// Logic: If user data exists in URL, it's a Signup flow
const flowType = (firstName && customerId) ? 'signup' : 'login';
// Store data for the final BYOS component
if (customerId) sessionStorage.setItem('signup_customer_id', customerId);
if (email) sessionStorage.setItem('signup_email', email);
if (firstName && lastName) sessionStorage.setItem('signup_name', ${firstName} ${lastName});
// 3. Generate PKCE & State
const codeVerifier = this.generateCodeVerifier();
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
// We prepend the flowType to the state so we know where to go on return
const randomState = this.generateState();
const combinedState = `${flowType}_${randomState}`;
sessionStorage.setItem('pkce_code_verifier', codeVerifier);
sessionStorage.setItem('pkce_state', combinedState);
const redirectUri = flowType === 'signup'
? ${window.location.origin}/exchange-token
: `${window.location.origin}/login-exchange-token`;
// 4. Redirect to OIDC Authorize
const authUrl = new URL(environment.descopeSignupAuthUrl);
authUrl.searchParams.append('client_id', environment.descopeProjectId);
// Redirect BACK to this same component (root '/')
authUrl.searchParams.append('redirect_uri', redirectUri);
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', combinedState);
window.location.href = authUrl.toString();
}
// --- PKCE Helpers ---
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);
}
}
then it goes to escope where need to define hosted url example http://localhost:4200/auth?flow=direct-signup-signin-flow-dev
then goes to this component
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-oidc-flow-host',
standalone: true,
template: <div style="text-align: center; margin-top: 50px; font-family: sans-serif;">
<p>Loading your custom experience...</p>
<div class="spinner"></div>
</div>,
styles: [.spinner { border: 4px solid #F3F3F3; border-top: 4px solid #3498DB; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }]
})
export class OidcFlowHostComponent implements OnInit {
constructor(private route: ActivatedRoute, private router: Router) {}
ngOnInit() {
// 1. Get the OIDC parameters Descope just sent to /auth
const oidcParams = this.route.snapshot.queryParams;
// 2. Retrieve the intent we saved in GatewayComponent
const savedState = sessionStorage.getItem('pkce_state');
// 3. Determine if we go to Signup or Login
let targetPath = '/auth/login'; // Default
if (savedState && savedState.startsWith('signup')) {
targetPath = '/signup';
}
// 4. Redirect to the actual UI component, passing along all OIDC parameters
// This is crucial so that the Descope SDK in those components knows
// it's part of an active OIDC transaction.
this.router.navigate([targetPath], {
queryParams: oidcParams,
queryParamsHandling: 'merge' // Keeps existing OIDC params in the URL
});
}
}
how can this be achieved from descopeDevRev
04/08/2026, 2:04 PMAksha Malik
04/08/2026, 2:05 PMAkash Jivane
04/08/2026, 2:22 PMredirect_uri from `window.location.origin`:
const redirectUri = flowType === 'signup'
? `${window.location.origin}/exchange-token`
: `${window.location.origin}/login-exchange-token`;
In Descope, a single federated app accepts a list of allowed redirect URIs. You just need to register all your environment URLs in that one app:
• <https://devmfecpe.yourdomain.com/exchange-token>
• <https://devmfecpe.yourdomain.com/login-exchange-token>
• <https://stagmfecpe.yourdomain.com/exchange-token>
• <https://stagmfecpe.yourdomain.com/login-exchange-token>
• <https://qamfecpe.yourdomain.com/exchange-token>
• <https://qamfecpe.yourdomain.com/login-exchange-token>
Descope will validate the redirect_uri in the request against this whitelist — one app handles all envs.
Since CPE and CPC are different products with separate user bases, I'd recommend 2 Descope projects (one per product). Within each project:
• 1 federated app covering all 3 envs via the multi-redirect-URI approach above
• Use Descope's project clone feature to copy your CPE config into CPC, then adjust as needed — this keeps them in sync without manual duplication
For the hosted flow URL per environment
In each Angular environment file, set descopeSignupAuthUrl to your Descope OIDC authorize endpoint with the right flow param:
// environment.dev.ts
export const environment = {
descopeSignupAuthUrl: '<https://api.descope.com/oauth2/v1/authorize>',
descopeProjectId: 'YOUR_PROJECT_ID',
flowId: 'direct-signup-signin-flow-dev' // env-specific flow
};
Then in GatewayComponent, append the flow to the auth URL:
const authUrl = new URL(environment.descopeSignupAuthUrl);
authUrl.searchParams.append('flow_id', environment.flowId); // pass env-specific flow
This cuts your federated app count from 6 down to 2, with zero code changes to your PKCE logic.
I hope this will help you to resolve your issue just let me know if this works!Aksha Malik
04/08/2026, 2:26 PMAkash Jivane
04/08/2026, 2:28 PMAksha Malik
04/15/2026, 6:50 AMAksha Malik
04/15/2026, 7:36 AMAkash Jivane
04/15/2026, 8:56 AM<http://devmfecpe.yourdomain.com|devmfecpe.yourdomain.com>, <http://stagmfecpe.yourdomain.com|stagmfecpe.yourdomain.com>, etc.). Descope will allow any path under those domains as a valid redirect target.
2. Managing Flow Hosting URLs across environments
Set the Flow Hosting URL in your federated app to Descope's hosted page:<https://auth.descope.io/><YOUR_PROJECT_ID>?flow=direct-signup-signin-flow-dev
This is environment-agnostic — auth runs on Descope's side, and after completion, users are redirected back to your Angular app via the dynamic redirect_uri your code already builds from window.location.origin. One app, no per-environment config needed.
Let me know if this answers your questions!Aksha Malik
04/15/2026, 9:00 AMAkash Jivane
04/15/2026, 9:12 AM<https://auth.cpe.yourdomain.com/auth>) and set that as the Flow Hosting URL in your federated app. This page runs the Descope web component with your custom screens — it doesn't need to be env-specific since the screens are the same across dev/stag/QA.
The env-specific part is already handled by redirect_uri in your PKCE code — after auth completes, Descope redirects the user back to whichever env triggered the flow (devmfecpe, stagmfecpe, etc.), all of which are whitelisted in Approved Domains.
So the final setup would be :
• 2 federated apps (one per project — CPE & CPC)
• Flow Hosting URL = one stable shared auth page per project
• Approved Domains = all env-specific domains whitelisted
• BYOS screens work as usual on the shared auth page
Does this setup work for you ?Aksha Malik
04/15/2026, 9:14 AMAkash Jivane
04/15/2026, 9:25 AM<http://devmfecpe.yourdomain.com/auth|devmfecpe.yourdomain.com/auth>), the only option in Descope is one federated app per environment — which is exactly the cost concern you started with.
To keep it to 2 apps, the shared auth page approach is the way to go. One practical tip: you can still pass the originating env in the PKCE state parameter (you're already doing flowType_randomState), and your shared auth page can read that to apply any env-specific config (e.g. different branding or flow IDs) without needing separate hosted URLs.
Hope that clears things up!Akash Jivane
04/15/2026, 1:33 PMAksha Malik
04/15/2026, 1:34 PMAkash Jivane
04/15/2026, 1:34 PM