# Sso Embed

> This skill allows agents to assist users in setting up, implementing, and troubleshooting Looker SSO (signed) and Cookieless embedding using the Looker Embed SDK.

- Skill: `lkrdev/sso-embed` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lkrdev/sso-embed`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lkrdev/sso-embed/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: lkrdev (https://skillmd.com/u/lkrdev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lkrdev/sso-embed

---


# Looker SSO Embed Skill

This skill allows agents to assist users in setting up, implementing, and troubleshooting Looker SSO (signed) and Cookieless embedding using the Looker Embed SDK.

## Overview
Looker embedding allows users to access private Looker content (Dashboards, Looks, Explores, Reports, etc.) within an external application. 
- **SSO (Signed) Embedding**: Authenticates users via a cryptographically signed URL generated by the parent application.
- **Cookieless Embedding**: Uses short-lived tokens to mitigate third-party cookie blocking, available in Looker 22.20+.

## 1. Looker Embed SDK (Recommended)
The [Looker JavaScript Embed SDK](https://github.com/looker-open-source/embed-sdk) is the preferred way to implement embedding.

### Installation & Initialization
```javascript
import { getEmbedSDK } from '@looker/embed-sdk'

// Initialize for SSO
getEmbedSDK().init('looker.example.com', '/auth')

// Initialize for Cookieless
getEmbedSDK().initCookieless(
  'looker.example.com',
  '/acquire-embed-session',
  '/generate-embed-tokens'
)
```

### Basic Implementation (SDK 2.0.0+)
SDK 2.0.0 introduces a unified connection that allows navigating between content types without recreating the iframe.
```javascript
const connection = await getEmbedSDK()
  .createDashboardWithId('42')
  .appendTo('#embed-container')
  .on('dashboard:run:start', () => console.log('Running...'))
  .withDynamicIFrameHeight() // Automatically adjust height
  .build()
  .connect()

// Interacting with the connection
if (connection.getPageType() === 'dashboards') {
  connection.asDashboardConnection().run()
}

// Navigating without iframe recreation
connection.loadDashboard('43')
connection.loadUrl('/embed/dashboards/44?state=California')
```

## 2. Signed URL Construction (The /auth Endpoint)
The backend `/auth` endpoint must generate a signed URL using the Looker Embed Secret.

### Signature String Order (Concatenate with `\n`):
1. `HOST` + `/login/embed/`
2. `EMBED_URL` (e.g., `/embed/dashboards/1`)
3. `nonce` (JSON string)
4. `time` (Integer)
5. `session_length` (Integer)
6. `external_user_id` (JSON string)
7. `permissions` (JSON array)
8. `models` (JSON array)
9. `group_ids` (JSON array)
10. `external_group_id` (JSON string)
11. `user_attributes` (JSON object)
12. `access_filters` (JSON object - `{}` if unused)

### Example Python Utility (server_utils/auth_utils.py):
```python
import hmac, hashlib, json, base64, time, binascii

def create_signed_url(src, user, host, secret):
    json_time = json.dumps(int(time.time()))
    json_nonce = json.dumps(binascii.hexlify(os.urandom(16)).decode())
    
    path = '/login/embed/' + quote(src)
    
    # Values must be in EXACT order
    string_to_sign = "\n".join([
        host + "/login/embed/",
        path,
        json_nonce,
        json_time,
        json.dumps(user['session_length']),
        json.dumps(user['external_user_id']),
        json.dumps(user['permissions']),
        json.dumps(user['models']),
        json.dumps(user.get('group_ids', [])),
        json.dumps(user.get('external_group_id', "")),
        json.dumps(user.get('user_attributes', {})),
        json.dumps(user.get('access_filters', {}))
    ])

    signer = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1)
    signature = base64.b64encode(signer.digest()).decode('utf-8')
    # Final URL construction includes all params + signature
```

## 2.1 Alternate Approach: Using Looker API (`create_sso_embed_url`)
Instead of manually constructing and signing the URL, you can use the Looker API endpoint `create_sso_embed_url` via the Looker Python SDK. This avoids manual signature generation errors.

### Example Python Implementation:
```python
import looker_sdk
from looker_sdk import models40 as models

# Initialize SDK (ensure LOOKERSDK_BASE_URL, LOOKERSDK_CLIENT_ID, LOOKERSDK_CLIENT_SECRET are set)
sdk = looker_sdk.init40()

def get_embed_url(src: str):
    base_url = os.environ.get("LOOKER_BASE_URL")
    target_url = f"{base_url}{src}"
    
    try:
        response = sdk.create_sso_embed_url(
            body=models.EmbedSsoParams(
                target_url=target_url,
                session_length=3600,
                external_user_id="user_1",
                permissions=["access_data", "see_user_dashboards", "see_lookml_dashboards"],
                models=["basic_ecomm"],
                group_ids=["22"],
                external_group_id="",
                user_attributes={},
                # Note: access_filters might not be supported as a keyword argument in some SDK versions
                force_logout_login=False,
            )
        )
        return response.url
    except Exception as e:
        print(f"Error: {e}")
        return None
```

## 3. Cookieless Embedding
Cookieless embedding requires two backend endpoints to manage sessions and tokens.
1. **Acquire Session**: Calls `acquire_embed_cookieless_session` API. Returns tokens (except `session_reference_token` which must be kept server-side).
2. **Generate Tokens**: Calls `generate_tokens_for_cookieless_session` when existing tokens are near expiration.

## 4. Advanced SDK Features
- **Dynamic Height**: `.withDynamicIFrameHeight()` handles iframe resizing based on content.
- **Full Screen**: `.withAllowAttr('fullscreen')` enables browser fullscreen mode for tiles.
- **Dialog Scroll**: `.withDialogScroll()` ensures Looker dialogs (like drills) are positioned correctly relative to the host scroll.
- **Merged Query Flow**: `.withMergedQueryEditFlow()` allows the host app to intercept and handle merged query editing.

## 5. Troubleshooting
- **Invalid Signature**: Most common issue. Verify parameter order, JSON stringification (especially empty objects/arrays), and encoding.
- **SDK Model Mismatch**: `EmbedSsoParams` might not accept `access_filters` as a keyword argument in some SDK versions (e.g., `EmbedSsoParams.__init__() got an unexpected keyword argument 'access_filters'`), even if documented. Try omitting it if it's empty.
- **Signature Quirks**: In manual signing, the string `/login/embed/` may need to appear in BOTH line 1 (Host + `/login/embed/`) and line 2 (Path) of the string to sign, depending on the example followed.
- **Third-Party Cookies**: If using SSO without vanity domains, browsers like Safari will block cookies. Use **Cookieless Embed** or **Vanity Domains** to resolve.
- **Domain Allowlist**: Ensure the host domain is in **Admin > Embed > Embedded Domain Allowlist**.
- **Whale/401 Errors**: Check if the Looker user has the `access_data` permission and access to the specified `models`.
- **SDK Logging**: Enable in browser console with `localStorage.debug = 'looker:chatty:*'`.

