Incorporação sem cookies

Quando o Looker é incorporado em um iframe usando a incorporação do Logon único (SSO), alguns navegadores adotam como padrão uma política de cookies que bloqueia cookies de terceiros. Os cookies de terceiros são rejeitados quando o iframe incorporado é carregado de um domínio diferente daquele que carrega o aplicativo de incorporação. Você pode solucionar essa limitação solicitando e usando um domínio de apresentação. No entanto, não é possível usar domínios personalizados em alguns cenários. Nesses casos, a incorporação sem cookies do Looker pode ser usada.

Como funciona a incorporação sem cookies?

Quando os cookies de terceiros não são bloqueados, um cookie de sessão é criado quando um usuário faz login no Looker. Esse cookie é enviado com todas as solicitações do usuário, e o servidor do Looker o utiliza para estabelecer a identidade do usuário que iniciou a solicitação. Quando os cookies são bloqueados, eles não são enviados com uma solicitação. Portanto, o servidor do Looker não consegue identificar o usuário associado à solicitação.

Para resolver esse problema, a incorporação sem cookies do Looker associa tokens a cada solicitação que pode ser usada para recriar a sessão do usuário no servidor do Looker. É responsabilidade do aplicativo de incorporação receber esses tokens e disponibilizá-los para a instância do Looker em execução no iframe incorporado. O processo de obtenção e fornecimento desses tokens é descrito no restante deste documento.

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

Como criar um iframe incorporado do Looker

O diagrama de sequência a seguir ilustra a criação de um iframe incorporado. Vários iframes podem ser gerados simultaneamente ou em algum momento no futuro. Quando implementado corretamente, o iframe se juntará automaticamente à sessão criada pelo primeiro. O SDK incorporado do Looker simplifica esse processo ao participar automaticamente da sessão existente.

  1. O usuário executa uma ação no aplicativo de incorporação que resulta na criação de um iframe do Looker.
  2. O cliente do aplicativo de incorporação adquire uma sessão do Looker. O SDK incorporado do Looker pode ser usado para iniciar esta sessão, mas é necessário fornecer um URL de endpoint ou uma função de callback. Se uma função de callback for usada, ela chamará o servidor do aplicativo de incorporação para adquirir a sessão de incorporação do Looker. Caso contrário, o SDK incorporado chamará o URL de endpoint fornecido.
  3. O servidor de aplicativos de incorporação usa a API Looker para adquirir uma sessão de incorporação. Essa chamada de API é semelhante ao processo de assinatura de SSO do Looker, porque aceita a definição do usuário incorporado como entrada. Se já houver uma sessão de incorporação do Looker para o usuário que fez a chamada, o token de referência da sessão associada precisa ser incluído na chamada. Isso será explicado com mais detalhes na seção Adquirir sessão deste documento.
  4. O processamento de endpoints da sessão de incorporação de aquisição é semelhante ao endpoint /login/embed/{signed url) assinado pelo SSO, porque espera que a definição de usuário de incorporação do Looker seja o corpo da solicitação, e não no URL. O processo de endpoint da sessão de aquisição adquire e cria ou atualiza o usuário incorporado. Ele também pode aceitar um token de referência de sessão existente. Isso é importante porque permite que vários iframes incorporados do Looker compartilhem a mesma sessão. O usuário incorporado não será atualizado se um token de referência de sessão for fornecido e a sessão não tiver expirado. Isso é compatível com o caso de uso em que um iframe é criado usando um URL de SSO assinado e outros iframes são criados sem um URL de SSO assinado. Nesse caso, os iframes sem URLs de SSO assinados herdarão o cookie da primeira sessão.
  5. A chamada da API Looker retorna quatro tokens, cada um com um time to live (TTL):
    • Token de autorização (TTL = 30 segundos)
    • Token de navegação (TTL = 10 minutos)
    • Token de API (TTL = 10 minutos)
    • Token de referência da sessão (TTL = ciclo de vida restante da sessão)
  6. O servidor de aplicativos de incorporação precisa rastrear os dados retornados pelos dados do Looker e associá-los ao usuário que faz a chamada e ao agente usuário do navegador do usuário que fez a chamada. As sugestões para fazer isso estão disponíveis na seção Gerar tokens deste documento. Essa chamada retornará o token de autorização, um token de navegação e um token de API, além de todos os TTLs associados. O token de referência da sessão deve ser protegido e não exposto no navegador de chamadas.
  7. Depois que os tokens forem retornados ao navegador, será necessário criar um URL de login incorporado do Looker. O SDK incorporado do Looker vai criar o URL de login incorporado automaticamente. Para usar a API windows.postMessage para construir o URL de login de incorporação, consulte a seção Como usar a API windows.postMessage do Looker deste documento para ver exemplos.

    O URL de login não contém os detalhes do usuário incorporado incorporado. Ele 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 precisa ser usado em até 30 segundos e só pode ser usado uma vez. Se iframes adicionais forem necessários, será preciso adquirir uma sessão de incorporação novamente. No entanto, se o token de referência da sessão for fornecido, o token de autorização será associado à mesma sessão.

  8. O endpoint de login incorporado do Looker determina se o login é para incorporação sem cookies, o que é indicado pela presença do token de autorização. Se o token de autorização for válido, ele verificará os seguintes itens:

    • A sessão associada ainda é válida.
    • O usuário de incorporação associado ainda é válido.
    • O user agent do navegador associado à solicitação corresponde ao user agent associado à sessão.
  9. Se as verificações da etapa anterior forem aprovadas, a solicitação será redirecionada com o URI de destino contido no URL. É o mesmo processo do login de SSO incorporado do Looker.

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

  11. Antes de o endpoint ser executado, o servidor do Looker procura o token de navegação na solicitação. Se o servidor encontrar o token, ele verificará:

    • A sessão associada ainda é válida.
    • O user agent do navegador associado à solicitação corresponde ao user agent associado à sessão.

    Se válida, a sessão será restaurada para a solicitação e a solicitação do painel será executada.

  12. O HTML para carregar o painel é retornado para o iframe.

  13. A IU do Looker em execução no iframe determina que o HTML do painel é uma resposta de incorporação sem cookies. Nesse ponto, a IU do Looker envia uma mensagem ao aplicativo de incorporação solicitando os tokens recuperados na etapa 6. A IU aguarda até receber os tokens. Se os tokens não chegarem, uma mensagem será exibida.

  14. O aplicativo de incorporação envia os tokens para o iframe incorporado do Looker.

  15. Quando os tokens são recebidos, a IU do Looker em execução no iframe inicia o processo para renderizar o objeto de solicitação. Durante esse processo, a UI faz chamadas de API ao servidor do Looker. O token de API recebido na etapa 15 é automaticamente injetado como um cabeçalho em todas as solicitações de API.

  16. Antes de qualquer endpoint ser executado, o servidor do Looker procura o token da API na solicitação. Se o servidor encontrar o token, ele verificará:

    • A sessão associada ainda é válida.
    • O user agent do navegador associado à solicitação corresponde ao user agent associado à sessão.

    Se a sessão for válida, ela será restaurada para a solicitação, e a solicitação de API será executada.

  17. Os dados do painel são retornados.

  18. O painel é renderizado.

  19. O usuário tem controle sobre o painel.

Como gerar novos tokens

  1. A IU do Looker em execução no iframe incorporado monitora o TTL dos tokens incorporados.
  2. Quando a expiração dos tokens se aproxima, a IU do Looker envia uma mensagem de token de atualização para o cliente do aplicativo de incorporação.
  3. O cliente do aplicativo de incorporação solicita novos tokens de um ponto de extremidade implementado no servidor de aplicativos de incorporação. O SDK do Looker para incorporação solicita novos tokens automaticamente, mas é preciso informar o URL do endpoint ou uma função de callback. Se a função de callback for usada, ela chamará o servidor do aplicativo de incorporação para gerar novos tokens. Caso contrário, o SDK incorporado chamará o URL de endpoint fornecido.
  4. O aplicativo de incorporação encontra o session_reference_token associado à sessão de incorporação. O exemplo fornecido no repositório Git do SDK do Looker Embed usa cookies de sessão, mas um cache distribuído do lado do servidor, como o Redis, também pode ser usado.
  5. O servidor do aplicativo de incorporação chama o servidor do Looker com uma solicitação para gerar tokens. Essa solicitação também requer tokens recentes de API e navegação, além do user agent do navegador que iniciou a solicitação.
  6. O servidor do Looker valida o user agent, o token de referência da sessão, o token de navegação e o token da API. Se a solicitação for válida, novos tokens serão gerados.
  7. Os tokens são retornados para o servidor de aplicativos de incorporação de chamadas.
  8. O servidor do aplicativo incorporado incorpora o token de referência da sessão da resposta e retorna a resposta restante para o cliente do aplicativo incorporado.
  9. O cliente do aplicativo de incorporação envia os tokens gerados recentemente para a IU do Looker. O SDK incorporado do Looker faz isso automaticamente. A incorporação de clientes de aplicativos que usam a API windows.postMessage será responsável por enviar os tokens. Depois que a IU do Looker receber os tokens, eles serão usados em chamadas de API e navegações de página subsequentes.

Como implementar a incorporação sem cookies do Looker

É possível implementar a incorporação sem cookies do Looker usando o SDK incorporado do Looker ou a API windows.postMessage. O método que usa o SDK de incorporação do Looker é mais fácil, mas um exemplo que mostra como usar a API windows.postMessage também está disponível. Explicações detalhadas das duas implementações podem ser encontradas no arquivo README do SDK incorporado do Looker. O repositório git SDK incorporado também contém implementações em funcionamento.

Como configurar a instância do Looker

A incorporação sem cookies tem algo em comum com a incorporação do Logon único (SSO) do Looker. A incorporação sem cookies depende da ativação da Autenticação via SSO. No entanto, ao contrário do SSO do Looker, a incorporação sem cookies não usa a configuração Incorporar Secret. A incorporação sem cookies usa um JSON Web Token (JWT) na forma de uma configuração Incorporar Secret JWT, que pode ser definida ou redefinida na página Incorporar na seção Plataforma do menu Administrador.

A definição do secret JWT não é necessária, uma vez que a primeira tentativa de criar uma sessão de incorporação sem cookies criará o JWT. Evite redefinir esse token, pois isso invalidará todas as sessões de incorporação sem cookies ativas.

Ao contrário da chave secreta incorporada, a chave secreta incorporada não é exposta porque é usada apenas internamente no servidor do Looker.

Implementação do cliente do aplicativo

Esta seção inclui exemplos de como implementar a incorporação sem cookies no cliente do aplicativo e contém as seguintes subseções:

Como instalar ou atualizar o SDK do Looker Embed

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

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

Como usar o SDK incorporado do Looker

Um novo método de inicialização foi adicionado ao SDK incorporado para iniciar a sessão sem cookies. Esse método aceita duas strings de URL ou duas funções de callback. As strings de URL precisam fazer referência aos endpoints no servidor de aplicativos de incorporação. Os detalhes de implementação desses endpoints no servidor de aplicativos são abordados na seção Implementação do servidor de aplicativos deste documento.

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

O exemplo a seguir mostra como os callbacks são usados. Os callbacks só devem ser usados quando for necessário que o aplicativo cliente de embedding esteja ciente do status da sessão de incorporação do Looker. Também é possível usar o evento session:status, o que torna desnecessário o uso de callbacks com o SDK incorporado.

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

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

Como usar a API Looker windows.postMessage

Veja um exemplo detalhado do uso da API windows.postMessage nos arquivos message_example.ts e message_utils.ts no repositório Git do SDK incorporado. Os destaques do exemplo são detalhados aqui.

O exemplo a seguir demonstra como criar o URL para o 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 a seguir demonstra como detectar solicitações de token, gerar novos tokens e enviá-los para o Looker. A função de callback é 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 aplicativos

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

Implementação básica

O aplicativo de incorporação é necessário para implementar dois endpoints do lado do servidor que invocarão os endpoints do Looker. Isso garante que o token de referência da sessão permaneça seguro. Estes são os endpoints:

  1. Adquirir sessão: se um token de referência de sessão já existir e ainda estiver ativo, as solicitações de uma sessão participarão da sessão existente. A sessão de aquisição é chamada quando um iframe é criado.
  2. Gerar tokens: o Looker aciona chamadas para este endpoint periodicamente.

Adquirir sessão

Este exemplo em TypeScript usa a sessão para salvar ou restaurar o token de referência da sessão. O endpoint não precisa ser implementado no 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 for adquirida. Essa é uma alternativa para adicionar o domínio incorporado usando o painel Administrador > Incorporar do Looker. O Looker salva o domínio incorporado no banco de dados interno do Looker. Portanto, ele não será exibido no painel Administrador > Incorporar. Em vez disso, o domínio de incorporação está associado à sessão sem cookies e permanece apenas durante a sessão. Analise as práticas recomendadas de segurança se você decidir aproveitar esse recurso.

Gerar tokens

Este exemplo em TypeScript usa a sessão para salvar ou restaurar o token de referência da sessão. O endpoint não precisa ser implementado no TypeScript.

É importante saber como lidar com 400 respostas, que ocorrem quando os tokens são inválidos. Isso não deve acontecer, mas, se isso acontecer, a prática recomendada é encerrar a sessão.

  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')) {
      // Currently the Looker UI does not know how to handle bad
      // tokens. This should not 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 de implementação

O aplicativo de incorporação deve acompanhar o token de referência da sessão e deve mantê-lo seguro. Esse token deve ser associado ao usuário do aplicativo incorporado. O token do aplicativo de incorporação pode ser armazenado de uma das seguintes maneiras:

  • Na sessão do usuário do aplicativo incorporado
  • Em um cache do servidor disponível em um ambiente em cluster
  • Em uma tabela de banco de dados associada ao usuário

Se a sessão for armazenada como um cookie, o cookie deverá ser criptografado. 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 expirar, uma caixa de diálogo será exibida no iframe incorporado. Neste ponto, o usuário não poderá fazer nada na instância incorporada. Quando isso ocorre, os eventos session:status são gerados, permitindo que o aplicativo de incorporação detecte o estado atual do aplicativo Looker incorporado e realize algum tipo de ação.

Como executar o exemplo de incorporação sem cookies do Looker

O repositório do SDK incorporado contém um servidor e um cliente de nó simples expressos em TypeScript que implementam um aplicativo de incorporação simples. Os exemplos mostrados anteriormente foram retirados desta implementação. Veja a seguir que a instância do Looker foi configurada para usar a incorporação sem cookies, conforme descrito anteriormente.

Você pode executar o servidor da seguinte maneira:

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

Configurar o servidor

Crie um arquivo .env na raiz do repositório clonado (incluído em .gitignore).

O formato é o seguinte:

LOOKER_EMBED_HOST=your-looker-instance-url.com.
LOOKER_EMBED_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