Looker Embedding & SSO Guide
Core Concepts
When embedding Looker dashboards into a frontend application (Node.js, Python, etc.), the backend must generate a signed SSO URL. This prevents users from having to log in to Looker directly.
Looker API 4.0 SSO Generation
To generate an SSO URL natively via the Looker API:
- Authenticate with
client_idandclient_secretto get a Bearer token. - Make a
POSTrequest to/api/4.0/embed/sso_url.
Required Permissions Array:
- To view dashboards:
["access_data", "see_looks", "see_user_dashboards", "download_with_limit", "see_drill_overlay"] - To enable Self-Service / Explore Menus: You MUST include
exploreandembed_browse_spaces. Withoutembed_browse_spaces, the user cannot navigate folders in the embed. - To enable Clear Cache & Refresh: Include
clear_cache_refresh. - To enable Gemini Insights (Conversational Analytics): Include
chat_with_explore,gemini_in_looker, andchat_with_agent.
Gemini Insights Embed Path
To embed the native Looker Gemini Conversational Analytics experience:
DO NOT use the old extension path (/embed/extensions/...).
DO use the native conversational path:
const target_url = `${LOOKER_BASE_URL}/embed/conversations`;
Implementation Template (Node.js / Express)
app.get('/api/looker/sso-url', async (req, res) => {
const dashboardId = req.query.dashboard_id; // e.g. "14"
const pathParam = req.query.path; // e.g. "/embed/conversations"
let target_url = dashboardId ?
`${LOOKER_BASE_URL}/embed/dashboards/${dashboardId}` :
`${LOOKER_BASE_URL}${pathParam}`;
const ssoBody = {
target_url: target_url,
session_length: 3600,
force_logout_login: true,
external_user_id: "embed_user_1",
first_name: "Embed",
last_name: "User",
// Crucial permissions for Gemini and Explore
permissions: [
"access_data", "see_looks", "see_user_dashboards", "explore",
"download_with_limit", "clear_cache_refresh", "see_drill_overlay",
"save_content", "embed_browse_spaces",
"chat_with_explore", "gemini_in_looker", "chat_with_agent"
],
models: ["your_model_name"], // Update this to match the LookML model!
group_ids: [],
external_group_id: "",
user_attributes: {},
access_filters: {}
};
const response = await axios.post(`${LOOKER_BASE_URL}/api/4.0/embed/sso_url`, ssoBody, {
headers: { 'Authorization': `token ${looker_api_token}` }
});
res.json({ url: response.data.url });
});
Pattern: Frontend Vanilla JS Iframe Embedding
When handling the frontend embedding, especially in a Single Page Application (SPA) or when toggling views, it is crucial to avoid refetching the SSO URL if the iframe is already loaded with a valid signed URL.
The Fix:
Always check if the iframe's src is completely empty OR if it lacks the signature parameter before fetching a new URL.
Example (Vanilla JS)
const iframe = document.querySelector('#my-looker-iframe');
// Correct Check: Only fetch if src is missing or doesn't have a signature
if (iframe && (!iframe.src || !iframe.src.includes('signature'))) {
try {
const res = await fetch('/api/looker/sso-url?dashboard_id=123');
if (res.ok) {
const data = await res.json();
iframe.src = data.url;
}
} catch (err) {
console.error('Error fetching SSO URL:', err);
}
}
Troubleshooting
- Dashboard fails to load: Check if the LookML model specified in the
modelsarray of the SSO payload matches the model the dashboard is built on. - Missing Options (Explore, Clear Cache): Ensure
embed_browse_spacesandclear_cache_refreshare in the permissions payload.