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
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! Go to 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
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