Incorporação sem cookies

Quando o Looker está incorporado num iFrame através da incorporação assinada, alguns navegadores usam por predefinição uma política de cookies que bloqueia cookies de terceiros. Os cookies de terceiros são rejeitados quando o iFrame incorporado é carregado a partir de um domínio diferente do domínio que carrega a aplicação de incorporação. Geralmente, pode contornar esta limitação pedindo e usando um domínio personalizado. No entanto, não é possível usar domínios personalizados em alguns cenários. É para estes cenários que pode usar a incorporação sem cookies do Looker.

Como funciona a incorporação sem cookies?

Quando os cookies de terceiros não estão bloqueados, é criado um cookie de sessão quando um utilizador inicia sessão no Looker. Este cookie é enviado com cada pedido do utilizador e o servidor do Looker usa-o para estabelecer a identidade do utilizador que iniciou o pedido. Quando os cookies estão bloqueados, o cookie não é enviado com um pedido, pelo que o servidor do Looker não consegue identificar o utilizador associado ao pedido.

Para resolver este problema, a incorporação sem cookies do Looker associa tokens a cada pedido que podem ser usados para recriar a sessão do utilizador no servidor do Looker. É da responsabilidade da aplicação de incorporação obter estes tokens e disponibilizá-los à instância do Looker que está a ser executada no iFrame incorporado. O processo de obtenção e disponibilização destes tokens é descrito no resto deste documento.

Para usar qualquer uma das APIs, a aplicação de incorporação tem de se conseguir autenticar na API Looker com privilégios de administrador. O domínio de incorporação também tem de estar listado na lista de autorizações de domínios de incorporação ou, se estiver a usar o Looker 23.8 ou posterior, o domínio de incorporação pode ser incluído quando a sessão sem cookies é adquirida.

Criar um iFrame incorporado do Looker

O diagrama de sequência seguinte ilustra a criação de um iFrame de incorporação. Podem ser gerados vários iFrames em simultâneo ou num momento futuro. Quando implementado corretamente, o iFrame junta-se automaticamente à sessão criada pelo primeiro iFrame. O SDK Looker Embed simplifica este processo ao associar automaticamente a sessão existente.

Um diagrama de sequência que ilustra a criação de um iFrame incorporado.

  1. O utilizador realiza uma ação na aplicação de incorporação que resulta na criação de um iFrame do Looker.
  2. O cliente da aplicação de incorporação adquire uma sessão do Looker. O SDK Looker Embed pode ser usado para iniciar esta sessão, mas tem de ser fornecido um URL do ponto final ou uma função de chamada de retorno. Se for usada uma função de chamada de retorno, esta chama o servidor da aplicação de incorporação para adquirir a sessão de incorporação do Looker. Caso contrário, o SDK de incorporação chama o URL do ponto final fornecido.
  3. O servidor da aplicação de incorporação usa a API Looker para adquirir uma sessão de incorporação. Esta chamada API é semelhante ao processo de assinatura de incorporação assinada do Looker, uma vez que aceita a definição do utilizador de incorporação como entrada. Se já existir uma sessão incorporada do Looker para o utilizador que está a fazer a chamada, o token de referência da sessão associado deve ser incluído na chamada. Isto é explicado mais detalhadamente na secção Adquirir sessão deste documento.
  4. O processamento do ponto final da sessão incorporada de aquisição é semelhante ao ponto final /login/embed/(signed url) assinado, uma vez que espera a definição do utilizador incorporado do Looker como o corpo do pedido, em vez de no URL. O processo do ponto final da sessão de incorporação de aquisição valida e, em seguida, cria ou atualiza o utilizador de incorporação. Também pode aceitar um token de referência de sessão existente. Isto é importante, uma vez que permite que vários iFrames incorporados do Looker partilhem a mesma sessão. O utilizador incorporado não é atualizado se for fornecido um token de referência de sessão e a sessão não tiver expirado. Isto suporta o exemplo de utilização em que um iFrame é criado com um URL de incorporação assinado e outros iFrames são criados sem um URL de incorporação assinado. Neste caso, os iFrames sem URLs incorporados assinados herdam o cookie da primeira sessão.
  5. A chamada API Looker devolve quatro tokens, cada um com um tempo de vida (TTL):
    • Símbolo de autorização (TTL = 30 segundos)
    • Token de navegação (TTL = 10 minutos)
    • Chave da API (TTL = 10 minutos)
    • Token de referência da sessão (TTL = tempo de vida restante da sessão)
  6. O servidor da aplicação de incorporação tem de monitorizar os dados devolvidos pelos dados do Looker e associá-los ao utilizador que está a chamar e ao agente do utilizador do navegador do utilizador que está a chamar. As sugestões sobre como o fazer são fornecidas na secção Gerar tokens deste documento. Esta chamada devolve o token de autorização, um token de navegação e um token de API, juntamente com todos os TTLs associados. O token de referência da sessão deve estar protegido e não exposto no navegador de chamada.
  7. Depois de os tokens serem devolvidos ao navegador, tem de ser construído um URL de início de sessão incorporado do Looker. O SDK Looker Embed cria automaticamente o URL de início de sessão de incorporação. Para usar a API windows.postMessage para criar o URL de início de sessão incorporado, consulte a secção Usar a API windows.postMessage do Looker deste documento para ver exemplos.

    O URL de início de sessão não contém os detalhes do utilizador incorporado com assinatura. Contém o URI de destino, incluindo o token de navegação e o token de autorização como um parâmetro de consulta. O token de autorização tem de ser usado no prazo de 30 segundos e só pode ser usado uma vez. Se forem necessários iFrames adicionais, tem de adquirir novamente uma sessão de incorporação. No entanto, se o token de referência da sessão for fornecido, o token de autorização é associado à mesma sessão.

  8. O ponto final de início de sessão de incorporação do Looker determina se o início de sessão é para uma incorporação sem cookies, o que é indicado pela presença do token de autorização. Se o token de autorização for válido, verifica o seguinte:

    • A sessão associada ainda é válida.
    • O utilizador de incorporação associado ainda é válido.
    • O agente do utilizador do navegador associado ao pedido corresponde ao agente do navegador associado à sessão.
  9. Se as verificações do passo anterior forem aprovadas, o pedido é redirecionado através do URI de destino contido no URL. Este é o mesmo processo que o início de sessão de incorporação assinada do Looker.

  10. Este pedido é o redirecionamento para iniciar o painel de controlo do Looker. Este pedido terá o token de navegação como um parâmetro.

  11. Antes de o ponto final ser executado, o servidor do Looker procura o token de navegação no pedido. Se o servidor encontrar o token, verifica o seguinte:

    • A sessão associada ainda é válida.
    • O agente do utilizador do navegador associado ao pedido corresponde ao agente do navegador associado à sessão.

    Se for válido, a sessão é restaurada para o pedido e o pedido do painel de controlo é executado.

  12. O HTML para carregar o painel de controlo é devolvido ao iFrame.

  13. A IU do Looker que está a ser executada no iFrame determina que o HTML do painel de controlo é uma resposta de incorporação sem cookies. Nesse momento, a IU do Looker envia uma mensagem à aplicação de incorporação para pedir os tokens que foram obtidos no passo 6. Em seguida, a IU aguarda até receber os tokens. Se os tokens não chegarem, é apresentada uma mensagem.

  14. A aplicação de incorporação envia os tokens para o iFrame incorporado do Looker.

  15. Quando os tokens são recebidos, a IU do Looker que está a ser executada no iFrame inicia o processo de renderização do objeto de pedido. Durante este processo, a IU faz chamadas à API para o servidor do Looker. O token da API que foi recebido no passo 15 é injetado automaticamente como um cabeçalho em todos os pedidos da API.

  16. Antes de qualquer ponto final ser executado, o servidor do Looker procura o token da API no pedido. Se o servidor encontrar o token, verifica o seguinte:

    • A sessão associada ainda é válida.
    • O agente do utilizador do navegador associado ao pedido corresponde ao agente do navegador associado à sessão.

    Se a sessão for válida, é restaurada para o pedido e o pedido da API é executado.

  17. Os dados do painel de controlo são devolvidos.

  18. O painel de controlo é renderizado.

  19. O utilizador tem controlo sobre o painel de controlo.

Gerar novos tokens

O diagrama de sequência seguinte ilustra a geração de novos tokens.

Um diagrama de sequência que ilustra a geração de novos tokens.

  1. A IU do Looker que está a ser executada no iFrame incorporado monitoriza o TTL dos tokens de incorporação.
  2. Quando os tokens se aproximam da data de validade, a IU do Looker envia uma mensagem de token de atualização ao cliente da aplicação de incorporação.
  3. Em seguida, o cliente da aplicação de incorporação pede novos tokens a um ponto final implementado no servidor da aplicação de incorporação. O SDK Looker Embed pede novos tokens automaticamente, mas tem de ser fornecido o URL do ponto final ou uma função de callback. Se a função de chamada de retorno for usada, chama o servidor da aplicação de incorporação para gerar novos tokens. Caso contrário, o SDK de incorporação chama o URL do ponto final fornecido.
  4. A aplicação de incorporação encontra o session_reference_token associado à sessão de incorporação. O exemplo fornecido no repositório Git do SDK Looker Embed usa cookies de sessão, mas também pode usar uma cache distribuída do lado do servidor, por exemplo, o Redis.
  5. O servidor da aplicação de incorporação chama o servidor do Looker com um pedido para gerar tokens. Este pedido também requer tokens de API e de navegação recentes, além do agente do utilizador do navegador que iniciou o pedido.
  6. O servidor do Looker valida o agente do utilizador, o token de referência da sessão, o token de navegação e o token da API. Se o pedido for válido, são gerados novos tokens.
  7. Os tokens são devolvidos ao servidor da aplicação de incorporação de chamadas.
  8. O servidor da aplicação de incorporação remove o token de referência da sessão da resposta e devolve a resposta restante ao cliente da aplicação de incorporação.
  9. O cliente da aplicação de incorporação envia os tokens recém-gerados para a IU do Looker. O SDK Looker Embed faz isto automaticamente. Os clientes de aplicações incorporadas que usam a API windows.postMessage são responsáveis pelo envio dos tokens. Assim que a IU do Looker receber os tokens, estes são usados em chamadas de API e navegações de páginas subsequentes.

Implementar a incorporação sem cookies do Looker

A incorporação sem cookies do Looker pode ser implementada através do SDK de incorporação do Looker ou da API windows.postMessage. Pode usar o método Looker Embed SDK, mas também está disponível um exemplo que mostra como usar a API windows.postMessage. Pode encontrar explicações detalhadas de ambas as implementações no ficheiro README do SDK de incorporação do Looker. O repositório git do SDK Embed também contém implementações funcionais.

Configurar a instância do Looker

A incorporação sem cookies tem semelhanças com a incorporação assinada do Looker. Para usar a incorporação sem cookies, um administrador tem de ativar a opção Incorporar autenticação de SSO. No entanto, ao contrário da incorporação assinada do Looker, a incorporação sem cookies não usa a definição Segredo de incorporação. A incorporação sem cookies usa um símbolo da Web JSON (JWT) sob a forma de uma definição de segredo do JWT de incorporação, que pode ser definida ou reposta na página Incorporar na secção Plataforma do menu Admin.

A definição do segredo do JWT não é necessária, uma vez que a primeira tentativa de criar uma sessão de incorporação sem cookies cria o JWT. Evite repor este token, uma vez que esta ação invalida todas as sessões de incorporação sem cookies ativas.

Ao contrário do segredo de incorporação, o segredo JWT de incorporação nunca é exposto, uma vez que só é usado internamente no servidor do Looker.

Implementação do cliente de aplicações

Esta secção inclui exemplos de como implementar a incorporação sem cookies no cliente da aplicação e contém as seguintes subsecções:

Instalar ou atualizar o SDK Looker Embed

As seguintes versões do SDK do Looker são necessárias para usar a incorporação sem cookies:

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

Usar o SDK Looker Embed

Foi adicionado um novo método de inicialização ao SDK incorporado para iniciar a sessão sem cookies. Este método aceita duas strings de URL ou duas funções de chamada de retorno. As strings de URL devem fazer referência a pontos finais no servidor da aplicação de incorporação. Os detalhes da implementação destes pontos finais no servidor de aplicações são abordados na secção Implementação do servidor de aplicações deste documento.

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

O exemplo seguinte mostra como são usados os callbacks. Os callbacks só devem ser usados quando for necessário que a aplicação cliente de incorporação saiba o estado da sessão de incorporação do Looker. Também pode usar o session:status evento, o que torna desnecessário usar callbacks com o 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
)

Usar a API windows.postMessage do Looker

Pode ver um exemplo detalhado da utilização da API windows.postMessage nos ficheiros message_example.ts e message_utils.ts no repositório Git do SDK Embed. Os destaques do exemplo são detalhados aqui.

O exemplo seguinte demonstra como criar o URL do iFrame. A função de callback é idêntica ao exemplo acquireEmbedSessionCallback visto anteriormente.

  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}`
  }

O exemplo seguinte demonstra como ouvir pedidos de tokens, gerar novos tokens e enviá-los para o Looker. A função de chamada de retorno é idêntica ao exemplo generateEmbedTokensCallback anterior.

      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
  }

Implementação do servidor de aplicações

Esta secção inclui exemplos de como implementar a incorporação sem cookies no servidor de aplicações e contém as seguintes subsecções:

Implementação básica

A aplicação de incorporação tem de implementar dois pontos finais do lado do servidor que invocam pontos finais do Looker. Isto destina-se a garantir que o token de referência da sessão permanece seguro. Estes são os pontos finais:

  1. Adquirir sessão: se já existir um token de referência de sessão e este ainda estiver ativo, os pedidos de uma sessão juntam-se à sessão existente. A sessão de aquisição é chamada quando é criado um iFrame.
  2. Gerar tokens: o Looker aciona chamadas para este ponto final periodicamente.

Adquira uma sessão

Este exemplo em TypeScript usa a sessão para guardar ou restaurar o token de referência da sessão. O ponto final não tem de ser implementado em 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
  }
}

A partir do Looker 23.8, o domínio de incorporação pode ser incluído quando a sessão sem cookies é adquirida. Esta é uma alternativa à adição do domínio de incorporação através do painel Administração > Incorporar do Looker. O Looker guarda o domínio de incorporação na base de dados interna do Looker, pelo que não é apresentado no painel Admin > Incorporar. Em alternativa, o domínio de incorporação está associado à sessão sem cookies e existe apenas durante a sessão. Reveja as práticas recomendadas de segurança se decidir tirar partido desta funcionalidade.

Gere tokens

Este exemplo em TypeScript usa a sessão para guardar ou restaurar o token de referência da sessão. O ponto final não tem de ser implementado em TypeScript.

É importante saber como processar respostas 400, que ocorrem quando os tokens são inválidos. Embora não deva ser devolvida uma resposta 400, se tal acontecer, a prática recomendada é terminar a sessão de incorporação do Looker. Pode terminar a sessão de incorporação do Looker destruindo o iFrame de incorporação ou definindo o valor session_reference_token_ttl como zero na mensagem session:tokens. Se definir o valor session_reference_token_ttl como zero, o iFrame do Looker apresenta uma caixa de diálogo de sessão expirada.

Não é devolvida uma resposta 400 quando a sessão de incorporação expira. Se a sessão de incorporação tiver expirado, é devolvida uma resposta 200 com o valor session_reference_token_ttl definido como zero.

  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
  }

Considerações relacionadas com a implementação

A aplicação de incorporação tem de monitorizar o token de referência da sessão e mantê-lo seguro. Este token deve estar associado ao utilizador da aplicação incorporada. O token da aplicação de incorporação pode ser armazenado de uma das seguintes formas:

  • Na sessão do utilizador da aplicação incorporada
  • Numa cache do lado do servidor que está disponível num ambiente agrupado
  • Numa tabela da base de dados associada ao utilizador

Se a sessão for armazenada como um cookie, o cookie deve ser encriptado. O exemplo no repositório do SDK incorporado usa um cookie de sessão para armazenar o token de referência da sessão.

Quando a sessão de incorporação do Looker expira, é apresentada uma caixa de diálogo no iFrame incorporado. Neste ponto, o utilizador não pode fazer nada na instância incorporada. Quando isto ocorre, os session:status eventos são gerados, o que permite à aplicação de incorporação detetar o estado atual da aplicação Looker incorporada e tomar algum tipo de ação.

Uma aplicação de incorporação pode detetar se a sessão de incorporação expirou verificando se o valor session_reference_token_ttl devolvido pelo ponto final generate_tokens é zero. Se o valor for zero, significa que a sessão de incorporação expirou. Considere usar uma função de callback para gerar tokens quando a incorporação sem cookies estiver a ser inicializada. A função de callback pode, então, determinar se a sessão de incorporação expirou e destrói o iFrame incorporado como alternativa à utilização da caixa de diálogo de sessão incorporada predefinida expirada.

Executar o exemplo de incorporação sem cookies do Looker

O repositório do SDK de incorporação contém um servidor e um cliente do Node Express escritos em TypeScript que implementam uma aplicação de incorporação. Os exemplos apresentados anteriormente são retirados desta implementação. O seguinte pressupõe que a sua instância do Looker foi configurada para usar a incorporação sem cookies, conforme descrito anteriormente.

Pode executar o servidor da seguinte forma:

  1. Clone o repositório do SDK Embed — git clone git@github.com:looker-open-source/embed-sdk.git
  2. Altere o diretório: cd embed-sdk
  3. Instale as dependências: npm install
  4. Configure o servidor, conforme mostrado na secção Configure o servidor deste documento.
  5. Execute o servidor: npm run server

Configure o servidor

Crie um ficheiro .env na raiz do repositório clonado (isto está incluído em .gitignore).

O formato é o seguinte:

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