Sign Out Button/Option for Experience Builder

7914
19
11-09-2020 03:28 PM
LARAPAdmin
Occasional Contributor

My organization loves the new Experience Builder application. They love how they can access multiple AGOL applications/tools and resources in one application. However, we are looking for the ability to sign out users. When my organization's users need to leave their desks, they often close their browsers to exit the application and ensure that they have signed out securely. Instead, they would like a button or an option to sign out of the application securely and take them to the sign in page of the application when they are ready to sign in again.

This would be a great enhancement to Experience Builder, especially as more organizations are using this application for everyday operations.

Thanks!

Carlos

19 Comments
Jianxia

@AudeBLEUZEN , the feature is under the consideration. However the priority in 2024 s to migrating WAB widgets over. See the 2024 roadmap.

johnbrosowsky

You can try putting a button on your experience with a label like "sign out" and configure the button to link to a URL and open in app window:

https://www.arcgis.com/sharing/rest/oauth2/signout?client_id=arcgisonline&redirect_uri=https://exper... 

 

AndreasEugster

Needed! Homogeneous across all esri apps (Storymap has it, Dashboards doesn't, ...).

It is criticized by our information security after pen-tests, so we actually have to manually squeeze a logout button / ref link into all these products now... would love to see this built in.

johnbrosowsky

@AndreasEugster - dashboard already has it built in.  Just add the header element to your dashboard and then you user can sign out from the menu button on the far right side of the dashboard header. 

PamelaLocke1

This really would be helpful!  Currently having an issues of people accessing an Experience Builder app through a mobile device and there is no option to sign in.  They have to log into portal first, then find the EXB. app, and then open it...too many steps. 

Joel
by

Yes, please add this.

Besides protecting maps from the wrong viewers, it helps with testing apps.

MattBell1

Please add this! Testing various users access to data and layers is a PITA without a sign out button. Such a pain to close out, clear cookies, and reopen.

peterverwey_ses

Hi there,

Fully support this request.

As a developer and user I work with a large range of user logins and a Sign In / Sign Out option is essential to manage my Experience Builder sessions.

Would love to see this in the next release.

Many thanks, Pete

DanielRivero

I did my own widget to logout, this do a similar popup that you have on the portal site. The only thing I did not have time is to override the onclick before the default widget window open. I would appreciate if anyone find that.

Note

 

import { React, getAppStore, appActions, type IMUser, type IMState, jsx, type AllWidgetProps } from 'jimu-core'
import { WidgetPlaceholder, Button, Card, CardBody, Popper } from 'jimu-ui'
import { useEffect, useState, useRef } from 'react'
import { useSelector } from 'react-redux'
import '../style.css'

export default function Widget (props: AllWidgetProps<any>) {
  const portalUrl = props.portalUrl?.toString()
  useEffect(() => {
    console.log('Widget montado en Experience Builder 1.15')
    //Uncomment this if you just want to logout directly (**)
    //handleLogout()
    getAppStore().dispatch(appActions.closeWidget(props.widgetId))
  }, [])

  useEffect(() => {
    console.log('Antes de obtener el controllerwidgetid' + props.widgetId)
    if (props.widgetId) {
      console.log('Seteando variable del controller')
      const btn = document.querySelector('[data-widgetid="' + props.widgetId + '"] button')
      if (btn) {
        const btnHtml = btn as HTMLElement
        setControllerBtn(btnHtml)
        setVisible(true)
        //Eliminamos cualquier 'onclick' que tenga el elemento, para evitar acumulación de eventos
        btnHtml.onclick = null
        btn.addEventListener('click', (event) => {
          event.stopPropagation() // Evita propagación de eventos
          toggleMenu()
        })
      }
    }
  }, [props.widgetId])

  const getClientId = () => {
    const state = getAppStore().getState()
    return state?.clientId || 'No se encontró Client ID'
  }

  const handleLogout = async () => {
    // :keycap_1: Hacer logout en ArcGIS
    const clientId = getClientId() // Tu client_id
    const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname) // Redirigir a la app
    //if (!getAppContext()?.isInBuilder) redirectUri = window.location.href
    const logoutUrl = `${portalUrl}/sharing/rest/oauth2/signout?client_id=${clientId}&redirect_uri=${redirectUri}`
    try {
      await fetch(logoutUrl, { credentials: 'include' })

      // :keycap_2: Borrar credenciales almacenadas
      document.cookie.split(';').forEach((c) => {
        document.cookie = c
          .replace(/^ +/, '')
          .replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
      })
      sessionStorage.clear()
      localStorage.clear()
      //if (!appContext.isInBuilder)
      // :keycap_3: Redirigir manualmente a la página de inicio
      window.location.href = window.location.origin + window.location.pathname
    } catch (error) {
      console.error('Error al cerrar sesión:', error)
    }
  }

  const [visible, setVisible] = useState(false)
  const [controllerBtn, setControllerBtn] = useState<HTMLElement | null>(null)
  // Obtener usuario autenticado desde ArcGIS Experience Builder
  const user: IMUser = useSelector((state: IMState) => state.user)
  const username = user?.username || 'Usuario desconocido'
  const fullName = user?.fullName || 'Usuario'
  const userThumbnail = user?.thumbnail
    ? `${portalUrl}/sharing/rest/community/users/${username}/info/${user.thumbnail}`
    : require('../anonimo.jpg')

  // Alternar visibilidad del menú
  const toggleMenu = () => {
    setVisible(!visible)
  }

  // Cierre de sesión
  const cerrarSesion = () => {
    const confirmar = window.confirm('¿Estás seguro de que deseas cerrar sesión?')
    if (confirmar) {
      handleLogout()
      setVisible(false)
    }
  }
  //Just return null if you only want logout, in that case don't forget to uncomment (**)
  //return null

  return (
    <div>
      {
        /* Menú desplegable desde el botón del controlador */
      }
      {controllerBtn && (
        <>
         {/* Agregar evento al botón del controlador para abrir el menú */}
      <Button onClick={toggleMenu} style={{ visibility: 'hidden' }}>
        Invisible Trigger
      </Button>
       {/* Popper anclado al botón del controlador */}
        <Popper reference={controllerBtn} open={visible} placement='bottom' modifiers={[
          { name: 'preventOverflow', options: { boundary: 'window' } },
          { name: 'offset', options: { offset: [0, 8] } }
        ]}>
          <Card className='user-menu'>
            <CardBody>
              <div className="d-flex justify-content-end">
                <Button onClick={() => { setVisible(false) }} className="close-btn">X</Button>
              </div>
              <div className='user-info'>
                <img src={userThumbnail} alt='Avatar' className='user-avatar-large' />
                <p className='user-name'><strong>{fullName}</strong></p>
                <p className='user-username'>{username}</p>
              </div>
              <hr />
              <p className='menu-option'>
                <a href={`${portalUrl}/home/user.html`} target="_blank" rel="noopener noreferrer">
                  Mi perfil
                </a>
              </p>
              <p className='menu-option'>
                <a href={`${portalUrl}/home/user.html#settings`} target="_blank" rel="noopener noreferrer">
                  Mi configuración
                </a>
              </p>
              <p className='menu-option'>
                <a href={`${portalUrl}/portalhelp/es/portal/`} target="_blank" rel="noopener noreferrer">
                  Ayuda
                </a>
              </p>
              <hr />
              <Button type='primary' onClick={cerrarSesion} block>
                Cerrar Sesión
              </Button>
            </CardBody>
          </Card>
        </Popper>
        </>
      )}
    </div>
  )
}