署名付き埋め込みを使用して Looker を iframe に埋め込んだ場合は、一部のブラウザはデフォルトでサードパーティの Cookie をブロックする Cookie ポリシーに設定されます。埋め込み iframe が、埋め込みアプリケーションを読み込むドメインとは異なるドメインから読み込まれると、サードパーティ Cookie が拒否されます。通常、この制限を回避するには、バニティ ドメインをリクエストして使用します。ただし、状況によってはバニティ ドメインを使用できない場合があります。このような場合は、Looker の Cookie を使用しない埋め込みを使用できます。
Cookie を使用しない埋め込みの仕組み
サードパーティの Cookie がブロックされない場合は、ユーザーが最初に Looker にログインするときにセッション Cookie が作成されます。この Cookie はユーザー リクエストごとに送信され、Looker サーバーはこれをリクエストを開始したユーザーの ID の確立に使用します。Cookie がブロックされている場合、Cookie はリクエストとともに送信されないため、Looker サーバーはリクエストに関連付けられているユーザーを識別できません。
この問題を解決するには、Looker の Cookie を使用しない埋め込みで、各リクエストにトークンを関連付けることで、Looker サーバーでのユーザー セッションの再作成に使用できます。これらのトークンを取得して、埋め込み iframe で実行されている Looker インスタンスで使用できるようにするのは、埋め込みアプリケーションの責任です。これらのトークンを取得して提供するプロセスについては、このドキュメントの後半で説明します。
どちらの API を使用する場合も、埋め込みアプリケーションが管理者権限で Looker API に認証されるようにする必要があります。また、埋め込みドメインは埋め込みドメイン許可リストに記載されているか、Looker 23.8 以降を使用している場合、Cookie のないセッションを取得する際に埋め込みドメインを含めることができます。
Looker 埋め込み iframe を作成する
次のシーケンス図は、埋め込み iframe を作成する流れを示しています。複数の iframe は、同時に、または今後のいずれかの時点で生成される可能性があります。正しく実装された場合、iframe は最初の iframe で作成されたセッションに自動的に参加します。Looker Embed SDK は、既存のセッションに自動的に接続することで、このプロセスを簡素化します。
- ユーザーが埋め込みアプリケーションで、Looker iframe を作成するアクションを実行しました。
- 埋め込みアプリケーション クライアントが Looker セッションを取得します。このセッションは Looker Embed SDK を使用して開始できますが、エンドポイント URL またはコールバック関数を指定する必要があります。コールバック関数を使用する場合は、埋め込みアプリケーション サーバーを呼び出して Looker 埋め込みセッションを取得します。それ以外の場合は、Embed SDK が指定されたエンドポイント URL を呼び出します。
- 埋め込みアプリケーション サーバーは、Looker API を使用して埋め込みセッションを取得します。この API 呼び出しは、埋め込みユーザー定義を入力として受け入れるため、Looker の署名付き埋め込み署名プロセスと類似しています。呼び出し元ユーザーに Looker 埋め込みセッションがすでに存在する場合は、関連するセッション参照トークンを呼び出しに含めます。これについては、このドキュメントのセッションを取得するセクションで詳しく説明します。
- 埋め込みセッションの取得エンドポイントの処理は、Looker の埋め込みユーザー定義を URL ではなくリクエストの本文に含めるという点で、署名付き
/login/embed/{signed url)
エンドポイントに類似しています。埋め込みセッション エンドポイント プロセスでは、埋め込みユーザーの検証と作成、または更新を行います。既存のセッション参照トークンも受け入れることができます。これにより、複数の Looker 埋め込み iframe が同じセッションを共有できるので重要です。セッション参照トークンが提供され、セッションが期限切れになっていない場合、埋め込みユーザーは更新されません。これは、1 つの iframe を署名付き埋め込み URL を使用して作成し、他の iframe を署名付き埋め込み URL を使用せずに作成するユースケースにも対応しています。この場合、署名付き埋め込み URL を使用しない iframe は、最初のセッションから Cookie を継承します。 - Looker API 呼び出しが 4 つのトークンを返し、それぞれに有効期間(TTL)が設定されています。
- 認証トークン(TTL = 30 秒)
- ナビゲーション トークン(TTL = 10 分)
- API トークン(TTL = 10 分)
- セッション参照トークン(TTL = セッションの残りの存続期間)
- 埋め込みアプリケーション サーバーは、Looker データから返されるデータを追跡し、呼び出し元のユーザーと呼び出し元ユーザーのブラウザのユーザー エージェントの両方に関連付ける必要があります。方法については、このドキュメントのトークンを生成するをご覧ください。この呼び出しは、認証トークン、ナビゲーション トークン、API トークンとともに、関連するすべての TTL を返します。セッション参照トークンを保護し、呼び出し元のブラウザで公開されないようにする必要があります。
トークンがブラウザに返されたら、Looker 埋め込みログイン URL を作成する必要があります。Looker Embed SDK では埋め込みログイン URL が自動で作成されます。
windows.postMessage
API を使用して埋め込みログイン URL を作成するには、このドキュメントの Lookerwindows.postMessage
API の使用セクションで例をご覧ください。ログイン URL には、署名付き埋め込みユーザーの詳細情報が含まれていません。これには、ナビゲーション トークンやクエリ パラメータとしての認可トークンなどのターゲット URI が含まれます。認証トークンは 30 秒以内に使用する必要があり、使用できるのは 1 回のみです。追加の iframe が必要な場合は、埋め込みセッションを再度取得する必要があります。ただし、セッション参照トークンが指定されている場合、認証トークンは同じセッションに関連付けられます。
Looker 埋め込みログイン エンドポイントは、ログインが Cookie を使用しない埋め込み用かどうかを決定します。これは認可トークンの存在によって示されます。認証トークンが有効な場合は、次のことを確認します。
- 関連付けられているセッションが引き続き有効である。
- 関連付けられている埋め込みユーザーが引き続き有効である。
- リクエストに関連付けられているブラウザ ユーザー エージェントが、セッションに関連付けられたブラウザ ユーザー エージェントと一致する。
前のステップのチェックに合格した場合、リクエストは、URL に含まれるターゲット URI を使用してリダイレクトされます。これは、Looker の署名付き埋め込みログインと同じプロセスです。
このリクエストは、Looker ダッシュボードを起動するためのリダイレクトです。このリクエストには、ナビゲーション トークンがパラメータとして含まれます。
エンドポイントが実行される前に、Looker サーバーはリクエスト内のナビゲーション トークンを探します。サーバーがトークンを検出すると、次のことを確認します。
- 関連付けられているセッションが引き続き有効である。
- リクエストに関連付けられているブラウザ ユーザー エージェントが、セッションに関連付けられたブラウザ ユーザー エージェントと一致する。
有効な場合、セッションがリクエストに対して復元され、ダッシュボード リクエストが実行されます。
ダッシュボードを読み込む HTML が iframe に返されます。
iframe で実行される Looker UI は、ダッシュボード HTML が Cookie を使用しない埋め込みレスポンスであると判断します。この時点で、Looker UI はステップ 6 で取得したトークンをリクエストする埋め込みアプリケーションにメッセージを送信します。トークンが受信されるまで UI は待機します。トークンを受信できない場合は、メッセージが表示されます。
埋め込みアプリケーションが、Looker に埋め込まれた iframe にトークンを送信します。
トークンを受信すると、iframe で実行されている Looker UI がリクエスト オブジェクトをレンダリングするプロセスを開始します。このプロセスでは、UI が Looker サーバーに API 呼び出しを行います。ステップ 15 で受信した API トークンは、すべての API リクエストにヘッダーとして自動的に挿入されます。
エンドポイントが実行される前に、Looker サーバーはリクエスト内の API トークンを探します。サーバーがトークンを検出すると、次のことを確認します。
- 関連付けられているセッションが引き続き有効である。
- リクエストに関連付けられているブラウザ ユーザー エージェントが、セッションに関連付けられたブラウザ ユーザー エージェントと一致する。
セッションが有効な場合、リクエストに対して復元され、API リクエストが実行されます。
ダッシュボードのデータが返されます。
ダッシュボードがレンダリングされます。
ユーザーがダッシュボードを管理できます。
新しいトークンの生成
次のシーケンス図は、新しいトークンを生成する流れ示しています。
- 埋め込み iframe で実行されている Looker UI は、埋め込みトークンの TTL をモニタリングします。
- トークンの有効期限が近づくと、Looker UI が埋め込みアプリケーション クライアントに更新トークンに関するメッセージを送信します。
- 埋め込みアプリケーション クライアントは、埋め込みアプリケーション サーバーで実装されているエンドポイントから新しいトークンをリクエストします。Looker Embed SDK は新しいトークンを自動的にリクエストしますが、エンドポイント URL またはコールバック関数を指定する必要があります。コールバック関数を使用する場合は、埋め込みアプリケーション サーバーを呼び出して新しいトークンを生成します。それ以外の場合は、Embed SDK が指定されたエンドポイント URL を呼び出します。
- 埋め込みアプリケーションは、埋め込みセッションに関連付けられている
session_reference_token
を見つけます。Looker Embedded SDK Git リポジトリに提供されている例では、セッション Cookie を使用しますが、分散サーバーサイド キャッシュ(Redis など)も使用できます。 - 埋め込みアプリケーション サーバーは、トークンの生成リクエストとともに Looker サーバーを呼び出します。このリクエストでは、リクエストを開始したブラウザのユーザー エージェントに加えて、最近の API トークンとナビゲーション トークンも必要です。
- Looker サーバーは、ユーザー エージェント、セッション参照トークン、ナビゲーション トークン、API トークンを検証します。リクエストが有効な場合、新しいトークンが生成されます。
- トークンは、呼び出し元の埋め込みアプリケーション サーバーに返されます。
- 埋め込みアプリケーション サーバーは、レスポンスからセッション参照トークンを削除し、残りのレスポンスを埋め込みアプリケーション クライアントに返します。
- 埋め込みアプリケーション クライアントが、新しく生成されたトークンを Looker UI に送信します。これは、Looker Embed SDK によって自動的に行われます。
windows.postMessage
API を使用する埋め込みアプリケーション クライアントがトークンの送信を行います。Looker UI はトークンを受信すると、後続の API 呼び出しとページ ナビゲーションで使用します。
Looker の Cookie を使用しない埋め込みの実装
Looker の Cookie を使用しない埋め込みは、Looker Embed SDK または windows.postMessage
API を使用して実装できます。Looker Embed SDK を使用する方法がより簡単ですが、windows.postMessage
API の使用方法を示す例も使用できます。両方の実装の詳細については、Looker の Embed SDK の README ファイルをご覧ください。Embed SDK の git リポジトリには、実際の実装も含まれています。
Looker インスタンスの構成
Cookie を使用しない埋め込みには、Looker の署名付き埋め込みとの共通点があります。Cookie を使用しない埋め込みでは、Embed SSO 認証を有効にする必要があります。ただし、Looker の署名付き埋め込みとは異なり、Cookie を使用しない埋め込みでは [シークレットを埋め込み] 設定を使用しません。Cookie を使用しない埋め込みでは、[Embed JWT Secret] 設定の形式で JSON Web Token(JWT)を使用します。これは [Admin] メニューの [Platform] セクションに表示される [Embed] ページで設定またはリセットできます。
Cookie を使用しない埋め込みセッションを最初に作成しようとすると、JWT が作成されるため、JWT のシークレットを設定することは必須ではありません。このトークンはリセットしないでください。リセットすると、アクティブな Cookie を使用しない埋め込みセッションがすべて無効となります。
埋め込みシークレットとは異なり、埋め込み JWT シークレットは Looker サーバーで内部でのみ使用されるため、公開されません。
アプリケーション クライアントの実装
このセクションには、アプリケーション クライアントに Cookie を使用しない埋め込みを実装する方法の例を記載しており、次のサブセクションを設けています。
Looker Embed SDK のインストールまたは更新
Cookie を使用しない埋め込みを使用するには、次の Looker SDK のバージョンが必要です。
@looker/embed-sdk >= 1.8
@looker/sdk >= 22.16.0
Looker Embed SDK を使用する
Embed SDK に新しい初期化メソッドが追加され、Cookie を使用しないセッションを開始できるようになりました。このメソッドは、2 つの URL 文字列または 2 つのコールバック関数を受け取ります。URL 文字列は、埋め込みアプリケーション サーバーのエンドポイントを参照する必要があります。アプリケーション サーバーにおけるこれらのエンドポイントの実装について詳しくは、このドキュメントのアプリケーション サーバーの実装セクションで説明します。
LookerEmbedSDK.initCookieless(
runtimeConfig.lookerHost,
'/acquire-embed-session',
'/generate-embed-tokens'
)
次の例は、コールバックの使用方法を示しています。コールバックは、埋め込みクライアント アプリケーションが Looker 埋め込みセッションのステータスを認識する必要がある場合にのみ使用してください。また、session:status
イベントを使用すると、Embed SDK でコールバックを使用する必要がなくなります。
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
)
Looker windows.postMessage
API の使用
windows.postMessage
API の使用例については、Embed SDK Git リポジトリの message_example.ts
ファイルと message_utils.ts
ファイルを参照してください。例のハイライトを以下に示します。
次の例は、iframe の URL を作成する方法を示しています。コールバック関数は、前述の acquireEmbedSessionCallback
の例と同じです。
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}`
}
次の例は、トークン リクエストをリッスンし、新しいトークンを生成して Looker に送信する方法を示しています。コールバック関数は、前の generateEmbedTokensCallback
の例と同じです。
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
}
アプリケーション サーバーの実装
このセクションには、アプリケーション サーバーに Cookie を使用しない埋め込みを実装する方法の例を記載しており、次のサブセクションを設けています。
基本的な実装
埋め込みアプリケーションは、Looker エンドポイントを呼び出す 2 つのサーバーサイド エンドポイントを実装する必要があります。これは、セッション参照トークンが安全な状態で保持されることを保証するためです。エンドポイントは次のとおりです。
- セッションを取得する - セッション参照トークンがすでに存在し、アクティブな場合、セッションのリクエストは既存のセッションに参加します。取得セッションは、iframe の作成時に呼び出されます。
- トークンを生成する - Looker が定期的にこのエンドポイントの呼び出しをトリガーします。
セッションを取得する
TypeScript のこの例では、セッションを使用してセッション参照トークンを保存または復元します。エンドポイントを 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
}
}
Looker 23.8 以降では、Cookie を使用しないセッションを取得する際に埋め込みドメインを含めることができます。これは、Looker の[管理者] > [埋め込み]パネルを使用して埋め込みドメインを追加する代わりに使用できます。Looker は埋め込みドメインを Looker の内部データベースに保存するため、[管理者] > [埋め込み] パネルには表示されません。代わりに、埋め込みドメインは Cookie を使用しないセッションに関連付けられ、セッションの間のみ存在します。この機能を利用する場合は、セキュリティのベスト プラクティスを確認してください。
トークンを生成する
TypeScript のこの例では、セッションを使用してセッション参照トークンを保存または復元します。エンドポイントを TypeScript に実装する必要はありません。
トークンが無効な場合に発生する 400 レスポンスを処理する方法を知っておくことが重要です。400 レスポンスが返されることはありませんが、返された場合は、Looker 埋め込みセッションを終了することをおすすめします。Looker 埋め込みセッションは、埋め込み iframe を破棄するか、session:tokens
メッセージで session_reference_token_ttl
値をゼロに設定することで終了できます。session_reference_token_ttl
値をゼロに設定すると、Looker iframe にセッションの期限切れダイアログが表示されます。
埋め込みセッションが有効期限切れになっても、400 レスポンスは返されません。埋め込みセッションが有効期限切れになった場合、session_reference_token_ttl
値がゼロに設定された 200 レスポンスが返されます。
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
}
実装に関する注意事項
埋め込みアプリケーションは、セッション参照トークンを追跡し保護する必要があります。このトークンは、埋め込みアプリケーションのユーザーに関連付けられている必要があります。埋め込みアプリケーションのトークンは、次のいずれかの方法で保存できます。
- 埋め込みアプリケーションのユーザー セッション
- クラスタ環境全体で使用できるサーバーサイド キャッシュ内
- ユーザーに関連付けられているデータベース テーブル内
セッションが Cookie として保存される場合は、Cookie を暗号化する必要があります。Embed SDK リポジトリの例では、セッション Cookie を使用してセッション参照トークンを保存しています。
Looker の埋め込みセッションが期限切れになると、埋め込み iframe にダイアログが表示されます。この時点では、埋め込みインスタンスに対しては何もアクションできません。この状態になると session:status
イベントが生成されます。埋め込みアプリケーションは、埋め込み Looker アプリケーションの現在の状態を検出し、なんらかのアクションを実行できます。
埋め込みアプリケーションは、generate_tokens
エンドポイントから返される session_reference_token_ttl
値がゼロかどうかを確認することで、埋め込みセッションが期限切れかどうかを検出できます。値がゼロの場合、埋め込みセッションは期限切れです。Cookie を使用しない埋め込みの初期化中にトークンを生成する場合は、コールバック関数の使用を検討してください。コールバック関数は、埋め込みセッションが期限切れかどうかを判断し、デフォルトの埋め込みセッション期限切れダイアログを使用する代わりに、埋め込み iframe を破棄します。
Looker の Cookie を使用しない埋め込みの実行例
Embed SDK リポジトリには、シンプルな埋め込みアプリケーションを実装する TypeScript で記述されたシンプルな Node express サーバーとクライアントが含まれています。前述の例はこの実装から取られています。以下の手順では、前述したように Looker インスタンスが Cookie を使用しない埋め込みを使用するように構成されていることを前提としています。
サーバーは次のように実行できます。
- Embed SDK リポジトリのクローンを作成する -
git clone git@github.com:looker-open-source/embed-sdk.git
- ディレクトリを変更する -
cd embed-sdk
- 依存関係をインストールする -
npm install
- このドキュメントのサーバーを構成するセクションの説明に従って、サーバーを構成する
- サーバーを実行する -
npm run server
サーバーを構成する
クローンされたリポジトリのルートに .env
ファイルを作成します(これは .gitignore
に含まれます)。
形式は次のとおりです。
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