Intégration sans cookie

Lorsque Looker est intégré dans un iFrame à l'aide de l'intégration signée, certains navigateurs utilisent par défaut une règle relative aux cookies qui bloque les cookies tiers. Les cookies tiers sont refusés lorsque l'iFrame intégré est chargé à partir d'un domaine différent de celui qui charge l'application d'intégration. Vous pouvez généralement contourner cette limite en demandant et en utilisant un domaine personnalisé. Toutefois, les domaines personnalisés ne peuvent pas être utilisés dans certains cas. C'est dans ces scénarios que l'intégration sans cookie de Looker peut être utilisée.

Comment fonctionne l'intégration sans cookie ?

Lorsque les cookies tiers ne sont pas bloqués, un cookie de session est créé lorsqu'un utilisateur se connecte à Looker pour la première fois. Ce cookie est envoyé avec chaque requête utilisateur. Le serveur Looker l'utilise pour établir l'identité de l'utilisateur qui a lancé la requête. Lorsque les cookies sont bloqués, ils ne sont pas envoyés avec une requête. Le serveur Looker ne peut donc pas identifier l'utilisateur associé à la requête.

Pour résoudre ce problème, l'intégration sans cookie de Looker associe des jetons à chaque requête, qui peuvent être utilisés pour recréer la session utilisateur sur le serveur Looker. Il incombe à l'application d'intégration d'obtenir ces jetons et de les mettre à la disposition de l'instance Looker exécutée dans l'iframe intégré. Le processus d'obtention et de fourniture de ces jetons est décrit dans le reste de ce document.

Pour utiliser l'une ou l'autre de ces API, l'application d'intégration doit pouvoir s'authentifier auprès de l'API Looker avec des droits d'administrateur. Le domaine d'intégration doit également figurer dans la liste d'autorisation des domaines d'intégration. Si vous utilisez Looker 23.8 ou version ultérieure, le domaine d'intégration peut être inclus lorsque la session sans cookie est acquise.

Créer un iFrame d'intégration Looker

Le diagramme de séquence suivant illustre la création d'un iframe d'intégration. Plusieurs iFrames peuvent être générés simultanément ou à un moment donné dans le futur. S'il est correctement implémenté, l'iFrame rejoindra automatiquement la session créée par le premier iFrame. Le SDK d'intégration Looker simplifie ce processus en rejoignant automatiquement la session existante.

Diagramme de séquence illustrant la création d'un iframe d'intégration.

  1. L'utilisateur effectue une action dans l'application d'intégration qui entraîne la création d'un iFrame Looker.
  2. Le client de l'application d'intégration acquiert une session Looker. Le SDK d'intégration Looker peut être utilisé pour lancer cette session, mais une URL de point de terminaison ou une fonction de rappel doivent être fournies. Si une fonction de rappel est utilisée, elle appellera le serveur d'application d'intégration pour acquérir la session d'intégration Looker. Sinon, le SDK d'intégration appellera l'URL du point de terminaison fournie.
  3. Le serveur d'application d'intégration utilise l'API Looker pour acquérir une session d'intégration. Cet appel d'API est semblable au processus de signature d'intégration signée de Looker, car il accepte la définition de l'utilisateur d'intégration en entrée. Si une session Looker intégrée existe déjà pour l'utilisateur appelant, le jeton de référence de session associé doit être inclus dans l'appel. Nous y reviendrons plus en détail dans la section Session "Acquisition" de ce document.
  4. Le traitement du point de terminaison de session d'intégration d'acquisition est semblable à celui du point de terminaison /login/embed/(signed url) signé, car il attend la définition de l'utilisateur Looker intégré en tant que corps de la requête, plutôt que dans l'URL. Le processus du point de terminaison de la session d'intégration d'acquisition valide, puis crée ou met à jour l'utilisateur intégré. Il peut également accepter un jeton de référence de session existant. C'est important, car cela permet à plusieurs iframes Looker intégrés de partager la même session. L'utilisateur intégré ne sera pas mis à jour si un jeton de référence de session est fourni et que la session n'a pas expiré. Cela permet d'utiliser un iFrame créé à l'aide d'une URL d'intégration signée et d'autres iFrames créés sans URL d'intégration signée. Dans ce cas, les iFrames sans URL d'intégration signées hériteront du cookie de la première session.
  5. L'appel d'API Looker renvoie quatre jetons, chacun avec une valeur TTL (Time To Live) :
    • Jeton d'autorisation (TTL = 30 secondes)
    • Jeton de navigation (TTL = 10 minutes)
    • Jeton d'API (TTL = 10 minutes)
    • Jeton de référence de session (TTL = durée de vie restante de la session)
  6. Le serveur d'application d'intégration doit suivre les données renvoyées par les données Looker et les associer à la fois à l'utilisateur appelant et à l'agent utilisateur du navigateur de l'utilisateur appelant. Vous trouverez des suggestions à ce sujet dans la section Générer des jetons de ce document. Cet appel renverra le jeton d'autorisation, un jeton de navigation et un jeton d'API, ainsi que toutes les valeurs TTL associées. Le jeton de référence de session doit être sécurisé et ne pas être exposé dans le navigateur appelant.
  7. Une fois les jetons renvoyés au navigateur, une URL de connexion Looker intégrée doit être créée. Le SDK Looker Embed construira automatiquement l'URL de connexion à l'intégration. Pour utiliser l'API windows.postMessage afin de créer l'URL de connexion intégrée, consultez la section Utiliser l'API windows.postMessage de Looker de ce document pour obtenir des exemples.

    L'URL de connexion ne contient pas les informations de l'utilisateur intégré signé. Il contient l'URI cible, y compris le jeton de navigation et le jeton d'autorisation en tant que paramètre de requête. Le jeton d'autorisation doit être utilisé dans les 30 secondes et ne peut l'être qu'une seule fois. Si des iFrames supplémentaires sont nécessaires, une nouvelle session d'intégration doit être acquise. Toutefois, si le jeton de référence de session est fourni, le jeton d'autorisation sera associé à la même session.

  8. Le point de terminaison de connexion Looker Embed détermine si la connexion est destinée à une intégration sans cookie, ce qui est indiqué par la présence du jeton d'autorisation. Si le jeton d'autorisation est valide, il vérifie les éléments suivants :

    • La session associée est toujours valide.
    • L'utilisateur intégré associé est toujours valide.
    • L'user-agent du navigateur associé à la requête correspond à celui associé à la session.
  9. Si les vérifications de l'étape précédente sont réussies, la requête est redirigée à l'aide de l'URI cible contenu dans l'URL. Il s'agit de la même procédure que pour la connexion à l'intégration signée Looker.

  10. Cette requête est la redirection permettant de lancer le tableau de bord Looker. Cette requête aura le jeton de navigation comme paramètre.

  11. Avant l'exécution du point de terminaison, le serveur Looker recherche le jeton de navigation dans la requête. Si le serveur trouve le jeton, il vérifie les éléments suivants :

    • La session associée est toujours valide.
    • L'user-agent du navigateur associé à la requête correspond à celui associé à la session.

    Si elle est valide, la session est restaurée pour la requête et la requête de tableau de bord est exécutée.

  12. Le code HTML permettant de charger le tableau de bord est renvoyé à l'iFrame.

  13. L'interface utilisateur Looker exécutée dans l'iFrame détermine que le code HTML du tableau de bord est une réponse d'intégration sans cookie. À ce stade, l'interface utilisateur Looker envoie un message à l'application d'intégration pour demander les jetons récupérés à l'étape 6. L'UI attend ensuite de recevoir les jetons. Si les jetons n'arrivent pas, un message s'affiche.

  14. L'application d'intégration envoie les jetons à l'iFrame Looker intégré.

  15. Une fois les jetons reçus, l'interface utilisateur Looker exécutée dans l'iFrame lance le processus de rendu de l'objet de requête. Au cours de ce processus, l'UI effectue des appels d'API au serveur Looker. Le jeton d'API reçu à l'étape 15 est automatiquement injecté en tant qu'en-tête dans toutes les requêtes d'API.

  16. Avant l'exécution d'un point de terminaison, le serveur Looker recherche le jeton API dans la requête. Si le serveur trouve le jeton, il vérifie les éléments suivants :

    • La session associée est toujours valide.
    • L'user-agent du navigateur associé à la requête correspond à celui associé à la session.

    Si la session est valide, elle est restaurée pour la requête et la requête API s'exécute.

  17. Les données du tableau de bord sont renvoyées.

  18. Le tableau de bord s'affiche.

  19. L'utilisateur contrôle le tableau de bord.

Générer des jetons

Le diagramme de séquence suivant illustre la génération de nouveaux jetons.

Diagramme de séquence illustrant la génération de jetons.

  1. L'interface utilisateur Looker exécutée dans l'iframe intégré surveille le TTL des jetons d'intégration.
  2. Lorsque les jetons approchent de leur date d'expiration, l'interface utilisateur Looker envoie un message de jeton d'actualisation au client de l'application d'intégration.
  3. Le client de l'application d'intégration demande ensuite de nouveaux jetons à un point de terminaison implémenté dans le serveur de l'application d'intégration. Le SDK Looker Embed demandera automatiquement de nouveaux jetons, mais l'URL du point de terminaison ou une fonction de rappel doivent être fournies. Si la fonction de rappel est utilisée, elle appellera le serveur de l'application d'intégration pour générer de nouveaux jetons. Sinon, le SDK d'intégration appellera l'URL du point de terminaison fournie.
  4. L'application d'intégration trouve le session_reference_token associé à la session d'intégration. L'exemple fourni dans le dépôt Git du SDK Looker Embed utilise des cookies de session, mais un cache distribué côté serveur (Redis, par exemple) peut également être utilisé.
  5. Le serveur d'application d'intégration appelle le serveur Looker avec une requête de génération de jetons. Cette requête nécessite également des jetons d'API et de navigation récents, en plus de l'user-agent du navigateur qui a lancé la requête.
  6. Le serveur Looker valide l'agent utilisateur, le jeton de référence de session, le jeton de navigation et le jeton d'API. Si la demande est valide, de nouveaux jetons sont générés.
  7. Les jetons sont renvoyés au serveur de l'application d'intégration appelante.
  8. Le serveur d'application d'intégration supprime le jeton de référence de session de la réponse et renvoie le reste de la réponse au client de l'application d'intégration.
  9. Le client de l'application d'intégration envoie les jetons nouvellement générés à l'interface utilisateur Looker. Le SDK d'intégration Looker le fera automatiquement. Les clients d'applications d'intégration qui utilisent l'API windows.postMessage seront responsables de l'envoi des jetons. Une fois que l'UI Looker reçoit les jetons, ils sont utilisés dans les appels d'API et les navigations sur les pages suivantes.

Implémenter l'intégration sans cookie de Looker

Vous pouvez implémenter l'intégration sans cookie de Looker à l'aide du SDK d'intégration Looker ou de l'API windows.postMessage. Vous pouvez utiliser la méthode Looker Embed SDK, mais un exemple montrant comment utiliser l'API windows.postMessage est également disponible. Des explications détaillées sur les deux implémentations sont disponibles dans le fichier README du SDK d'intégration de Looker. Le dépôt Git du SDK Embed contient également des implémentations fonctionnelles.

Configurer l'instance Looker

L'intégration sans cookie présente des points communs avec l'intégration signée de Looker. Pour utiliser l'intégration sans cookie, un administrateur doit activer l'authentification SSO pour l'intégration. Toutefois, contrairement à l'intégration signée Looker, l'intégration sans cookie n'utilise pas le paramètre Secret d'intégration. L'intégration sans cookie utilise un jeton Web JSON (JWT) sous la forme d'un paramètre Secret JWT d'intégration, qui peut être défini ou réinitialisé sur la page Intégrer dans la section Plate-forme du menu Admin.

Il n'est pas nécessaire de définir le secret JWT, car la toute première tentative de création d'une session d'intégration sans cookie créera le JWT. Évitez de réinitialiser ce jeton, car cela invaliderait toutes les sessions d'intégration sans cookie actives.

Contrairement au secret d'intégration, le secret JWT d'intégration n'est jamais exposé, car il n'est utilisé qu'en interne sur le serveur Looker.

Implémentation du client d'application

Cette section inclut des exemples d'implémentation de l'intégration sans cookie dans le client d'application et contient les sous-sections suivantes :

Installer ou mettre à jour le SDK d'intégration Looker

Les versions suivantes du SDK Looker sont requises pour utiliser l'intégration sans cookie :

@looker/embed-sdk >= 2.0.0
@looker/sdk >= 22.16.0

Utiliser le SDK d'intégration Looker

Une nouvelle méthode d'initialisation a été ajoutée au SDK d'intégration pour lancer la session sans cookie. Cette méthode accepte deux chaînes d'URL ou deux fonctions de rappel. Les chaînes d'URL doivent faire référence à des points de terminaison dans le serveur d'application d'intégration. Les détails d'implémentation de ces points de terminaison sur le serveur d'application sont abordés dans la section Implémentation du serveur d'application de ce document.

getEmbedSDK().initCookieless(
  runtimeConfig.lookerHost,
  '/acquire-embed-session',
  '/generate-embed-tokens'
)

L'exemple suivant montre comment les rappels sont utilisés. Les rappels ne doivent être utilisés que lorsque l'application cliente d'intégration doit connaître l'état de la session d'intégration Looker. Vous pouvez également utiliser l'événement session:status, ce qui rend inutile l'utilisation de rappels avec le SDK Embed.

const acquireEmbedSessionCallback =
  async (): Promise<LookerEmbedCookielessSessionData> => {
    const resp = await fetch('/acquire-embed-session')
    if (!resp.ok) {
      console.error('acquire-embed-session failed', { resp })
      throw new Error(
        `acquire-embed-session failed: ${resp.status} ${resp.statusText}`
      )
    }
    return (await resp.json()) as LookerEmbedCookielessSessionData
  }

const generateEmbedTokensCallback =
  async ({ api_token, navigation_token }): Promise<LookerEmbedCookielessSessionData> => {
    const resp = await fetch('/generate-embed-tokens', {
      method: 'PUT',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ api_token, navigation_token }),
    })
    if (!resp.ok) {
      console.error('generate-embed-tokens failed', { resp })
      throw new Error(
        `generate-embed-tokens failed: ${resp.status} ${resp.statusText}`
      )
    }
    return (await resp.json()) as LookerEmbedCookielessSessionData
  }

getEmbedSDK().initCookieless(
  runtimeConfig.lookerHost,
  acquireEmbedSessionCallback,
  generateEmbedTokensCallback
)

Utiliser l'API windows.postMessage de Looker

Vous pouvez consulter un exemple détaillé d'utilisation de l'API windows.postMessage dans les fichiers message_example.ts et message_utils.ts du dépôt Git Embed SDK. Les points clés de l'exemple sont détaillés ici.

L'exemple suivant montre comment créer l'URL de l'iFrame. La fonction de rappel est identique à l'exemple acquireEmbedSessionCallback vu précédemment.

  private async getCookielessLoginUrl(): Promise<string> {
    const { authentication_token, navigation_token } =
      await this.embedEnvironment.acquireSession()
    const url = this.embedUrl.startsWith('/embed')
      ? this.embedUrl
      : `/embed${this.embedUrl}`
    const embedUrl = new URL(url, this.frameOrigin)
    if (!embedUrl.searchParams.has('embed_domain')) {
      embedUrl.searchParams.set('embed_domain', window.location.origin)
    }
    embedUrl.searchParams.set('embed_navigation_token', navigation_token)
    const targetUri = encodeURIComponent(
      `${embedUrl.pathname}${embedUrl.search}${embedUrl.hash}`
    )
    return `${embedUrl.origin}/login/embed/${targetUri}?embed_authentication_token=${authentication_token}`
  }

L'exemple suivant montre comment écouter les demandes de jetons, générer de nouveaux jetons et les envoyer à Looker. La fonction de rappel est identique à l'exemple generateEmbedTokensCallback précédent.

      this.on(
        'session:tokens:request',
        this.sessionTokensRequestHandler.bind(this)
      )

  private connected = false

  private async sessionTokensRequestHandler(_data: any) {
    const contentWindow = this.getContentWindow()
    if (contentWindow) {
      if (!this.connected) {
        // When not connected the newly acquired tokens can be used.
        const sessionTokens = this.embedEnvironment.applicationTokens
        if (sessionTokens) {
          this.connected = true
          this.send('session:tokens', this.embedEnvironment.applicationTokens)
        }
      } else {
        // If connected, the embedded Looker application has decided that
        // it needs new tokens. Generate new tokens.
        const sessionTokens = await this.embedEnvironment.generateTokens()
        this.send('session:tokens', sessionTokens)
      }
    }
  }

  send(messageType: string, data: any = {}) {
    const contentWindow = this.getContentWindow()
    if (contentWindow) {
      const message: any = {
        type: messageType,
        ...data,
      }
      contentWindow.postMessage(JSON.stringify(message), this.frameOrigin)
    }
    return this
  }

Implémentation du serveur d'application

Cette section inclut des exemples d'implémentation de l'intégration sans cookie dans le serveur d'application. Elle contient les sous-sections suivantes :

Implémentation de base

L'application d'intégration doit implémenter deux points de terminaison côté serveur qui appelleront les points de terminaison Looker. Cela permet de s'assurer que le jeton de référence de session reste sécurisé. Voici les points de terminaison :

  1. Acquérir une session : si un jeton de référence de session existe déjà et est toujours actif, les demandes de session rejoindront la session existante. La session d'acquisition est appelée lorsqu'un iFrame est créé.
  2. Générez des jetons : Looker appelle ce point de terminaison périodiquement.

Acquérir une session

Cet exemple en TypeScript utilise la session pour enregistrer ou restaurer le jeton de référence de session. Le point de terminaison n'a pas besoin d'être implémenté en TypeScript.

  app.get(
    '/acquire-embed-session',
    async function (req: Request, res: Response) {
      try {
        const current_session_reference_token =
          req.session && req.session.session_reference_token
        const response = await acquireEmbedSession(
          req.headers['user-agent']!,
          user,
          current_session_reference_token
        )
        const {
          authentication_token,
          authentication_token_ttl,
          navigation_token,
          navigation_token_ttl,
          session_reference_token,
          session_reference_token_ttl,
          api_token,
          api_token_ttl,
        } = response
        req.session!.session_reference_token = session_reference_token
        res.json({
          api_token,
          api_token_ttl,
          authentication_token,
          authentication_token_ttl,
          navigation_token,
          navigation_token_ttl,
          session_reference_token_ttl,
        })
      } catch (err: any) {
        res.status(400).send({ message: err.message })
      }
    }
  )

async function acquireEmbedSession(
  userAgent: string,
  user: LookerEmbedUser,
  session_reference_token: string
) {
  await acquireLookerSession()
    try {
    const request = {
      ...user,
      session_reference_token: session_reference_token,
    }
    const sdk = new Looker40SDK(lookerSession)
    const response = await sdk.ok(
      sdk.acquire_embed_cookieless_session(request, {
        headers: {
          'User-Agent': userAgent,
        },
      })
    )
    return response
  } catch (error) {
    console.error('embed session acquire failed', { error })
    throw error
  }
}

À partir de Looker 23.8, le domaine d'intégration peut être inclus lors de l'acquisition de la session sans cookie. Il s'agit d'une alternative à l'ajout du domaine d'intégration à l'aide du panneau Admin > Intégrer de Looker. Looker enregistre le domaine d'intégration dans la base de données interne de Looker. Il n'apparaît donc pas dans le panneau Admin > Embed (Admin > Intégrer). Le domaine d'intégration est associé à la session sans cookie et n'existe que pendant la durée de la session. Si vous décidez d'utiliser cette fonctionnalité, consultez les bonnes pratiques de sécurité.

Générer des jetons

Cet exemple en TypeScript utilise la session pour enregistrer ou restaurer le jeton de référence de session. Le point de terminaison n'a pas besoin d'être implémenté en TypeScript.

Il est important de savoir comment gérer les réponses 400, qui se produisent lorsque les jetons ne sont pas valides. Bien qu'une réponse 400 ne devrait pas se produire, il est recommandé de mettre fin à la session Looker intégrée si cela arrive. Vous pouvez mettre fin à la session d'intégration Looker en détruisant l'iFrame d'intégration ou en définissant la valeur session_reference_token_ttl sur zéro dans le message session:tokens. Si vous définissez la valeur session_reference_token_ttl sur zéro, l'iFrame Looker affiche une boîte de dialogue indiquant que la session a expiré.

Une réponse 400 n'est pas renvoyée lorsque la session d'intégration expire. Si la session d'intégration a expiré, une réponse 200 est renvoyée avec la valeur session_reference_token_ttl définie sur zéro.

  app.put(
    '/generate-embed-tokens',
    async function (req: Request, res: Response) {
      try {
        const session_reference_token = req.session!.session_reference_token
        const { api_token, navigation_token } = req.body as any
        const tokens = await generateEmbedTokens(
          req.headers['user-agent']!,
          session_reference_token,
          api_token,
          navigation_token
        )
        res.json(tokens)
      } catch (err: any) {
        res.status(400).send({ message: err.message })
      }
    }
  )
}
async function generateEmbedTokens(
  userAgent: string,
  session_reference_token: string,
  api_token: string,
  navigation_token: string
) {
  if (!session_reference_token) {
    console.error('embed session generate tokens failed')
    // missing session reference  treat as expired session
    return {
      session_reference_token_ttl: 0,
    }
  }
  await acquireLookerSession()
  try {
    const sdk = new Looker40SDK(lookerSession)
    const response = await sdk.ok(
      sdk.generate_tokens_for_cookieless_session(
        {
          api_token,
          navigation_token,
          session_reference_token: session_reference_token || '',
        },
        {
          headers: {
            'User-Agent': userAgent,
          },
        }
      )
    )
    return {
      api_token: response.api_token,
      api_token_ttl: response.api_token_ttl,
      navigation_token: response.navigation_token,
      navigation_token_ttl: response.navigation_token_ttl,
      session_reference_token_ttl: response.session_reference_token_ttl,
    }
  } catch (error: any) {
    if (error.message?.includes('Invalid input tokens provided')) {
      // The Looker UI does not know how to handle bad
      // tokens. This shouldn't happen but if it does expire the
      // session. If the token is bad there is not much that that
      // the Looker UI can do.
      return {
        session_reference_token_ttl: 0,
      }
    }
    console.error('embed session generate tokens failed', { error })
    throw error
  }

Observations relatives à la mise en œuvre

L'application d'intégration doit suivre le jeton de référence de session et le sécuriser. Ce jeton doit être associé à l'utilisateur de l'application intégrée. Le jeton d'application d'intégration peut être stocké de l'une des manières suivantes :

  • Dans la session utilisateur de l'application intégrée
  • Dans un cache côté serveur disponible dans un environnement en cluster
  • Dans une table de base de données associée à l'utilisateur

Si la session est stockée sous forme de cookie, celui-ci doit être chiffré. L'exemple du dépôt SDK Embed utilise un cookie de session pour stocker le jeton de référence de session.

Lorsqu'une session Looker intégrée expire, une boîte de dialogue s'affiche dans l'iframe intégré. À ce stade, l'utilisateur ne pourra rien faire dans l'instance intégrée. Dans ce cas, les événements session:status sont générés, ce qui permet à l'application d'intégration de détecter l'état actuel de l'application Looker intégrée et de prendre une mesure.

Une application d'intégration peut détecter si la session d'intégration a expiré en vérifiant si la valeur session_reference_token_ttl renvoyée par le point de terminaison generate_tokens est égale à zéro. Si la valeur est zéro, cela signifie que la session d'intégration a expiré. Envisagez d'utiliser une fonction de rappel pour générer des jetons lors de l'initialisation de l'intégration sans cookie. La fonction de rappel peut ensuite déterminer si la session d'intégration a expiré et détruira l'iframe intégré au lieu d'utiliser la boîte de dialogue par défaut "Session d'intégration expirée".

Exécuter l'exemple d'intégration sans cookie Looker

Le dépôt du SDK d'intégration contient un serveur Node Express et un client écrit en TypeScript qui implémentent une application d'intégration. Les exemples présentés précédemment sont tirés de cette implémentation. Les informations suivantes supposent que votre instance Looker a été configurée pour utiliser l'intégration sans cookie, comme décrit précédemment.

Vous pouvez exécuter le serveur comme suit :

  1. Clonez le dépôt du SDK d'intégration : git clone git@github.com:looker-open-source/embed-sdk.git
  2. Changer de répertoire : cd embed-sdk
  3. Installez les dépendances : npm install
  4. Configurez le serveur, comme indiqué dans la section Configurer le serveur de ce document.
  5. Exécutez le serveur : npm run server

Configurer le serveur

Créez un fichier .env à la racine du dépôt cloné (inclus dans .gitignore).

Le format est le suivant :

LOOKER_WEB_URL=your-looker-instance-url.com
LOOKER_API_URL=https://your-looker-instance-url.com
LOOKER_DEMO_HOST=localhost
LOOKER_DEMO_PORT=8080
LOOKER_EMBED_SECRET=embed-secret-from-embed-admin-page
LOOKER_CLIENT_ID=client-id-from-user-admin-page
LOOKER_CLIENT_SECRET=client-secret-from-user-admin-page
LOOKER_DASHBOARD_ID=id-of-dashboard
LOOKER_LOOK_ID=id-of-look
LOOKER_EXPLORE_ID=id-of-explore
LOOKER_EXTENSION_ID=id-of-extension
LOOKER_VERIFY_SSL=true
LOOKER_REPORT_ID=id-of-report
LOOKER_QUERY_VISUALIZATION_ID=id-of-query-visualization