Build looker embed url

Hi,

We were using the `create_sso_embed_url` (https://developers.looker.com/api/explorer/4.0/methods/Auth/create_sso_embed_url) API endpoint to sign a dashboard embed URL. But we need to sign multiple URLs and the API calls are not so fast.

We've seen we can self-sign the embed URL by using the `Embed Secret` but the API documentation is a bit blurry (https://cloud.google.com/looker/docs/single-sign-on-embedding#creating_the_embed_url)

- It doesn't indicate which `hmac` algorithm to use. (`SHA256`?)
- It's a bit unclear about how to structure the args to sign

Here is a sample of the string we using to sign:

 

 

xxx.cloud.looker.com
/login/embed/%2Fembed%2Fdashboards%2Fcustomer_analytics%3A%3Auser_login
"22b1ee700ef3dc2f500fb7"
1686656824
43200
"xxx-1-1"
["access_data","see_drill_overlay","see_lookml_dashboards","see_looks","see_user_dashboards"]
["xxx"]
["28"]
""
{}
{}

 

 

with annotation of what they are

 

 

xxx.cloud.looker.com # host
/login/embed/%2Fembed%2Fdashboards%2Fcustomer_analytics%3A%3Auser_login # the dashboard path URL encoded prefixed with /login/embed
"22b1ee700ef3dc2f500fb7" # nonce in json
1686656824 # timestamp in json
43200 # session length in json
"xxx-1-1" # external_user_id in json
["access_data","see_drill_overlay","see_lookml_dashboards","see_looks","see_user_dashboards"] # permissions in json
["xxx"] # models in json
["28"] # group_ids in json
"" # external_group_id in json
{} # user_attributes in json
{} # access_filters in json

 

 

Then we're using generating the hmac from this string with

 

 

secret = "Looker Embed Secret"
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), secret, string_to_sign)

 

 

But when validating the URL we keep having : "'signature' param failed to authenticate: 'xxxx'

Does anyone have a better example / doc to provide?

We found this doc given a JS version of the building URL

https://blog.montrealanalytics.com/limit5-how-to-embed-looker-content-in-a-web-application-edb8b5f07...

But it seems to use a different format for the second arg (the dashboard path) (it prefixes it `/login/embed`) when the doc says it should be the host which should be suffixed with `/login/embed`

Any ideas?

Thank you

Solved Solved
0 2 649
1 ACCEPTED SOLUTION

Found a Ruby implementation, there is some errors in the Looker docs (HOST vs HOST/login/embed), the sha is not specified, and such..

require 'cgi'
require 'securerandom'
require 'uri'
require 'base64'
require 'json'
require 'openssl'
module LookerEmbedClient
  def self.created_signed_embed_url(options)
    # looker options
    secret = options[:secret]
    host = options[:host]
    # user options
    json_external_user_id = options[:external_user_id].to_json
    json_first_name = options[:first_name].to_json
    json_last_name = options[:last_name].to_json
    json_permissions = options[:permissions].to_json
    json_models = options[:models].to_json
    json_group_ids = options[:group_ids].to_json
    json_external_group_id = options[:external_group_id].to_json
    json_user_attributes = options[:user_attributes].to_json
    json_access_filters = options[:access_filters].to_json
    # url/session specific options
    embed_path = '/login/embed/' + CGI.escape(options[:embed_url])
    json_session_length = options[:session_length].to_json
    json_force_logout_login = options[:force_logout_login].to_json
    # computed options
    json_time = Time.now.to_i.to_json
    json_nonce = SecureRandom.hex(16).to_json
    # compute signature
    string_to_sign = ""
    string_to_sign += host + "\n"
    string_to_sign += embed_path + "\n"
    string_to_sign += json_nonce + "\n"
    string_to_sign += json_time + "\n"
    string_to_sign += json_session_length + "\n"
    string_to_sign += json_external_user_id + "\n"
    string_to_sign += json_permissions + "\n"
    string_to_sign += json_models + "\n"
    # optionally add settings not supported in older Looker versions
    string_to_sign += json_group_ids + "\n" if options[:group_ids]
    string_to_sign += json_external_group_id+ "\n" if options[:external_group_id]
    string_to_sign += json_user_attributes + "\n" if options[:user_attributes]
    string_to_sign += json_access_filters
    signature = Base64.encode64(
      OpenSSL::HMAC.digest(
        OpenSSL::Digest.new('sha1'),
        secret,
        string_to_sign.force_encoding("utf-8"))).strip
    # construct query string
    query_params = {
      nonce: json_nonce,
      time: json_time,
      session_length: json_session_length,
      external_user_id: json_external_user_id,
      permissions: json_permissions,
      models: json_models,
      access_filters: json_access_filters,
      first_name: json_first_name,
      last_name: json_last_name,
      force_logout_login: json_force_logout_login,
      signature: signature
    }
    # add optional parts as appropriate
    query_params[:group_ids] = json_group_ids if options[:group_ids]
    query_params[:external_group_id] = json_external_group_id if options[:external_group_id]
    query_params[:user_attributes] = json_user_attributes if options[:user_attributes]
    query_params[:user_timezone] = options[:user_timezone].to_json if options.has_key?(:user_timezone)
    query_string = URI.encode_www_form(query_params)
    "#{host}#{embed_path}?#{query_string}"
  end
end
def sample
  fifteen_minutes = 15 * 60
  #YOU NEED TO MODIFY THE BELOW CODE AS PER YOUR REQUIRMENT.
  url_data = {
    host: 'instancename.cloud.looker.com',
    secret: 'c17001c68440f858f4fed4',
    external_user_id: '57',
    first_name: 'Sagar',
    last_name: 'choudhary',
    permissions: ['see_user_dashboards', 'see_lookml_dashboards', 'access_data', 'see_looks'], #dedicated Permission Set
    models: ['Model-name'],
    group_ids: [],
    external_group_id: '',
    user_attributes: {},
    access_filters: {},
    session_length: fifteen_minutes,
    embed_url: "/embed/dashboards/143",
    force_logout_login: true
  }
  url = LookerEmbedClient::created_signed_embed_url(url_data)
  puts "https://#{url}"
end
sample()

source: https://blog.searce.com/single-sign-on-sso-embedding-in-looker-fa4ccc467ded

View solution in original post

2 REPLIES 2

We've also try

xxx.cloud.looker.com/login/embed/
/embed/dashboards/customer_analytics::user_login
"xxx"
1686662409
43200
"xxx-1-1"
["access_data","see_drill_overlay","see_lookml_dashboards","see_looks","see_user_dashboards"]
["xxx"]
["28"]
""
{}
{}

Found a Ruby implementation, there is some errors in the Looker docs (HOST vs HOST/login/embed), the sha is not specified, and such..

require 'cgi'
require 'securerandom'
require 'uri'
require 'base64'
require 'json'
require 'openssl'
module LookerEmbedClient
  def self.created_signed_embed_url(options)
    # looker options
    secret = options[:secret]
    host = options[:host]
    # user options
    json_external_user_id = options[:external_user_id].to_json
    json_first_name = options[:first_name].to_json
    json_last_name = options[:last_name].to_json
    json_permissions = options[:permissions].to_json
    json_models = options[:models].to_json
    json_group_ids = options[:group_ids].to_json
    json_external_group_id = options[:external_group_id].to_json
    json_user_attributes = options[:user_attributes].to_json
    json_access_filters = options[:access_filters].to_json
    # url/session specific options
    embed_path = '/login/embed/' + CGI.escape(options[:embed_url])
    json_session_length = options[:session_length].to_json
    json_force_logout_login = options[:force_logout_login].to_json
    # computed options
    json_time = Time.now.to_i.to_json
    json_nonce = SecureRandom.hex(16).to_json
    # compute signature
    string_to_sign = ""
    string_to_sign += host + "\n"
    string_to_sign += embed_path + "\n"
    string_to_sign += json_nonce + "\n"
    string_to_sign += json_time + "\n"
    string_to_sign += json_session_length + "\n"
    string_to_sign += json_external_user_id + "\n"
    string_to_sign += json_permissions + "\n"
    string_to_sign += json_models + "\n"
    # optionally add settings not supported in older Looker versions
    string_to_sign += json_group_ids + "\n" if options[:group_ids]
    string_to_sign += json_external_group_id+ "\n" if options[:external_group_id]
    string_to_sign += json_user_attributes + "\n" if options[:user_attributes]
    string_to_sign += json_access_filters
    signature = Base64.encode64(
      OpenSSL::HMAC.digest(
        OpenSSL::Digest.new('sha1'),
        secret,
        string_to_sign.force_encoding("utf-8"))).strip
    # construct query string
    query_params = {
      nonce: json_nonce,
      time: json_time,
      session_length: json_session_length,
      external_user_id: json_external_user_id,
      permissions: json_permissions,
      models: json_models,
      access_filters: json_access_filters,
      first_name: json_first_name,
      last_name: json_last_name,
      force_logout_login: json_force_logout_login,
      signature: signature
    }
    # add optional parts as appropriate
    query_params[:group_ids] = json_group_ids if options[:group_ids]
    query_params[:external_group_id] = json_external_group_id if options[:external_group_id]
    query_params[:user_attributes] = json_user_attributes if options[:user_attributes]
    query_params[:user_timezone] = options[:user_timezone].to_json if options.has_key?(:user_timezone)
    query_string = URI.encode_www_form(query_params)
    "#{host}#{embed_path}?#{query_string}"
  end
end
def sample
  fifteen_minutes = 15 * 60
  #YOU NEED TO MODIFY THE BELOW CODE AS PER YOUR REQUIRMENT.
  url_data = {
    host: 'instancename.cloud.looker.com',
    secret: 'c17001c68440f858f4fed4',
    external_user_id: '57',
    first_name: 'Sagar',
    last_name: 'choudhary',
    permissions: ['see_user_dashboards', 'see_lookml_dashboards', 'access_data', 'see_looks'], #dedicated Permission Set
    models: ['Model-name'],
    group_ids: [],
    external_group_id: '',
    user_attributes: {},
    access_filters: {},
    session_length: fifteen_minutes,
    embed_url: "/embed/dashboards/143",
    force_logout_login: true
  }
  url = LookerEmbedClient::created_signed_embed_url(url_data)
  puts "https://#{url}"
end
sample()

source: https://blog.searce.com/single-sign-on-sso-embedding-in-looker-fa4ccc467ded