上一篇 實作單點登入 (SSO) 登入功能 (上) 我們介紹了 SSO 的流程以及 Electron & SSO 的登入流程及機制,接下來筆者會示範一個簡單的範例,呈現如何在 Electron 建立一個 window 並載入 React 元件,此元件包含一個登入按鈕,按下後會導向 SSO 的登入網址,進行登入流程。
我們的實作流程會參考 Postman 的流程,按下主頁登入按鈕後開啟系統瀏覽器,此處會使用 Google Auth 還做示範
先前的文章有提到如何開啟一個 window,此處主要呈現元件的部分
const LoginPage = (): JSX.Element => {
  const [isFirstLogin, _setIsFirstLogin] = useState<boolean>(true)
  const { toastState, showToast, setErrorToast } = useToast()
  const [isLoading, setIsLoading] = useState<boolean>(false)
  const goToDirectPage = async (): Promise<void> => {
    const data: AuthPkceResp = await api.auth.getAuthPkce()
    const { codeChallenge, codeChallengeMethod } = data
    const url = `${import.meta.env.VITE_IDENTITY_DOMAIN}`
    await window.openExternal(url)
  }
  const handleLogin = async (): Promise<void> => {
    try {
      setIsLoading(true)
      goToDirectPage()
    } catch (error) {
      setErrorToast('Error! Please try again.')
    } finally {
      setIsLoading(false)
    }
  }
  return (
    <$.Wrapper>
      {showToast && <Toast message={toastState.message} status={toastState.status} />}
      <CloseButton />
      <$.Logo src={logoIcon} alt='Logo' />
      {isFirstLogin ? <FirstLogin onLogin={handleLogin} isLoading={isLoading} /> : <ReLogin onLogin={handleLogin} />}
    </$.Wrapper>
  )
}
export default LoginPage
在全域的 window 註冊 openExternal API 給 renderer 使用
declare global {
  interface Window {
    electron: ElectronAPI
    injectData: any
    windowBounds?: {
      x: number
      y: number
      width: number
      height: number
    }
    ipcInvoke: (channel: string, ...args: any[]) => Promise<any>
    ipcOn: (channel: string, listener: (event: any, data: any) => void) => void
    ipcSend: (channel: string, data?: any) => void
    openExternal: (url: string, options?: Electron.OpenExternalOption) => Promise<void>
    reduxtron: PreloadReduxBridgeReturn<State, Action>['handlers']
    dataStore: { get: (key: string) => any; set: (key: string, val: any) => void }
  }
}
const FirstLogin = ({ onLogin, isLoading = false }): JSX.Element => {
  return (
    <>
      <$.LoginButton loading={isLoading} colorType={BUTTON_TYPE.BLUE} onClick={onLogin}>
        Log in with browser
      </$.LoginButton>
    </>
  )
}
實作登入 google 的網頁並放在 VITE_IDENTITY_DOMAIN env file
在 main 主程序加上以下程式
app.setAsDefaultProtocolClient('googleLogin')
/* Later */
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
設定自定義協定的主程式名稱,當網頁回應用程式時會抓取這個名稱的應用程式
app.setAsDefaultProtocolClient('googleLogin')
app.on('open-url', (_event, url) => {
  const accessTokenRegex = /accessToken=([^&]+)/
  const accessTokenMatch = url.match(accessTokenRegex)
  const accessToken = accessTokenMatch ? accessTokenMatch[1] : null
  electronStore.set('accessToken', accessToken)
  showDialogWindow()
})
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
當導回來應用程式的時候會觸發這個事件並取得字串
此處會帶 token 回應用程式並進行字串處理並儲存至 Electron Store
此篇文章實作了一個簡易的 Google Login 流程,當使用者登入 google 後可以回到應用程式,透過這個方式,桌面應用程式可以透過外部瀏覽器進行 SSO 登入