Select to view content in your preferred language

Blocking end-users from seeing application until after login

271
2
Jump to solution
10-07-2024 10:12 AM
ericsamson_tract
Regular Contributor

Hey all,

I have created several experience builder applications using the developer version. These have a lot of custom widgets that I would prefer the public to not be able to view. My application is hosted and currently accessible from anyone with access to the URL. They can't see any of the data, since they need to be logged into our org to see all the good stuff.

However, as of right now, the application will load all aspects of the application except for the data, allowing anyone to see things like the custom widgets (although they won't run) and our custom layer lists (which does list what data we use).

While I understand that anyone could simply right click and dig into the source code for these if they wanted to, I was wondering if anyone has had any luck displaying a blank screen until authentication has been completed? Or have any ideas how I would implement that

Web appbuilder developer edition does this by default when deployed, it's frustrating that experience builder doesn't out of the box.

Thanks,

0 Kudos
1 Solution

Accepted Solutions
ericsamson_tract
Regular Contributor

In case anyone else finds this, I was able to figure it out. My solution involves simply blocking the following css classes if the user is not authenticated:

'controller-container' and 'widget-content'
 

Of course people can work around this if they really want to, so take it with a grain of salt. It works for my purposes. I wrote the following code and put it within the index.html file:

<script>
	const moduleScript = document.createElement('script');
	moduleScript.type = 'module';
	moduleScript.textContent = `
		// Import required modules using SystemJS
		System.import('jimu-core').then((jimuCore) => {
			const { getAppStore, SessionManager } = jimuCore;
			
			let isAuthenticated = false;

			const setElementVisibility = (element, visible) => {
				element.style.setProperty('display', visible ? 'block' : 'none', 'important');
			};

			const handleElements = (visible) => {
				const widgetElements = document.getElementsByClassName('controller-container');
				const cacheElements = document.getElementsByClassName('widget-content');
				
				Array.from(widgetElements).forEach(element => setElementVisibility(element, visible));
				Array.from(cacheElements).forEach(element => setElementVisibility(element, visible));
			};

			const observer = new MutationObserver((mutations) => {
				mutations.forEach((mutation) => {
					if (mutation.addedNodes.length) {
						mutation.addedNodes.forEach((node) => {
							if (node.classList) {
								if (node.classList.contains('controller-container') ||
									node.classList.contains('widget-content')) {
									setElementVisibility(node, isAuthenticated);
								}
							}
						});
					}
				});
			});

			observer.observe(document.body, {
				childList: true,
				subtree: true
			});

			const checkAuth = () => {
				try {
					const userInfo = getAppStore().getState().user;
					const session = SessionManager.getInstance().getMainSession();
					const authCover = document.getElementById('auth-cover');

					if (userInfo && session && session.token) {
						console.log("User authenticated:", userInfo.username);
						isAuthenticated = true;
						
						if (authCover) {
							authCover.style.opacity = '0';
							setTimeout(() => {
								authCover.remove();
							}, 300);
						}
						
						handleElements(true);
						return true;
					} else {
						console.log("User not authenticated");
						isAuthenticated = false;
						
						if (authCover && !document.body.contains(authCover)) {
							document.body.insertBefore(authCover, document.body.firstChild);
							authCover.style.opacity = '1';
						}
						
						handleElements(false);
						return false;
					}
				} catch (error) {
					console.warn("Error checking authentication:", error);
					isAuthenticated = false;
					return false;
				}
			};

			// resize window catch
			let resizeTimeout;
			window.addEventListener('resize', () => {
				clearTimeout(resizeTimeout);
				resizeTimeout = setTimeout(() => {
					if (isAuthenticated) {
						handleElements(true);
					} else {
						handleElements(false);
					}
				}, 100);
			});

			handleElements(false);

			// check on authentication 
			let attempts = 0;
			const maxAttempts = 30;
			const interval = setInterval(() => {
				attempts++;
				if (checkAuth() || attempts >= maxAttempts) {
					clearInterval(interval);
				}
			}, 1000);
		}).catch(error => {
			console.error("Error importing jimu-core:", error);
		});
	`;

	window.addEventListener('load', function() {
		const checkSystemJS = setInterval(() => {
			if (window.System) {
				clearInterval(checkSystemJS);
				document.body.appendChild(moduleScript);
			}
		}, 100);
	});
</script>

 

View solution in original post

2 Replies
Wei_Ying
Esri Regular Contributor

Hi @ericsamson_tract ,

Thanks for your feedback! Since you deploy the app on your own server, instead of ArcGIS platform, so we cannot simply use the auth process from ArcGIS. 

You may try to set up a credential requirement for your server, so when user accessing your server, they need a credential when visiting the URL. 
So far there is no easy way to do from ExB. Otherwise, you may customize a landing page with customized login widget to ask user for credential, where after user logged in redirect them to the true app.  
We are actually working on a Login widget, which might help solve your case in future. 

Thanks,
Wei 

 

 

0 Kudos
ericsamson_tract
Regular Contributor

In case anyone else finds this, I was able to figure it out. My solution involves simply blocking the following css classes if the user is not authenticated:

'controller-container' and 'widget-content'
 

Of course people can work around this if they really want to, so take it with a grain of salt. It works for my purposes. I wrote the following code and put it within the index.html file:

<script>
	const moduleScript = document.createElement('script');
	moduleScript.type = 'module';
	moduleScript.textContent = `
		// Import required modules using SystemJS
		System.import('jimu-core').then((jimuCore) => {
			const { getAppStore, SessionManager } = jimuCore;
			
			let isAuthenticated = false;

			const setElementVisibility = (element, visible) => {
				element.style.setProperty('display', visible ? 'block' : 'none', 'important');
			};

			const handleElements = (visible) => {
				const widgetElements = document.getElementsByClassName('controller-container');
				const cacheElements = document.getElementsByClassName('widget-content');
				
				Array.from(widgetElements).forEach(element => setElementVisibility(element, visible));
				Array.from(cacheElements).forEach(element => setElementVisibility(element, visible));
			};

			const observer = new MutationObserver((mutations) => {
				mutations.forEach((mutation) => {
					if (mutation.addedNodes.length) {
						mutation.addedNodes.forEach((node) => {
							if (node.classList) {
								if (node.classList.contains('controller-container') ||
									node.classList.contains('widget-content')) {
									setElementVisibility(node, isAuthenticated);
								}
							}
						});
					}
				});
			});

			observer.observe(document.body, {
				childList: true,
				subtree: true
			});

			const checkAuth = () => {
				try {
					const userInfo = getAppStore().getState().user;
					const session = SessionManager.getInstance().getMainSession();
					const authCover = document.getElementById('auth-cover');

					if (userInfo && session && session.token) {
						console.log("User authenticated:", userInfo.username);
						isAuthenticated = true;
						
						if (authCover) {
							authCover.style.opacity = '0';
							setTimeout(() => {
								authCover.remove();
							}, 300);
						}
						
						handleElements(true);
						return true;
					} else {
						console.log("User not authenticated");
						isAuthenticated = false;
						
						if (authCover && !document.body.contains(authCover)) {
							document.body.insertBefore(authCover, document.body.firstChild);
							authCover.style.opacity = '1';
						}
						
						handleElements(false);
						return false;
					}
				} catch (error) {
					console.warn("Error checking authentication:", error);
					isAuthenticated = false;
					return false;
				}
			};

			// resize window catch
			let resizeTimeout;
			window.addEventListener('resize', () => {
				clearTimeout(resizeTimeout);
				resizeTimeout = setTimeout(() => {
					if (isAuthenticated) {
						handleElements(true);
					} else {
						handleElements(false);
					}
				}, 100);
			});

			handleElements(false);

			// check on authentication 
			let attempts = 0;
			const maxAttempts = 30;
			const interval = setInterval(() => {
				attempts++;
				if (checkAuth() || attempts >= maxAttempts) {
					clearInterval(interval);
				}
			}, 1000);
		}).catch(error => {
			console.error("Error importing jimu-core:", error);
		});
	`;

	window.addEventListener('load', function() {
		const checkSystemJS = setInterval(() => {
			if (window.System) {
				clearInterval(checkSystemJS);
				document.body.appendChild(moduleScript);
			}
		}, 100);
	});
</script>