# Netflix Android 認証フロー: API 詳細 > [auth_flow_android.md](auth_flow_android.md) のフロー概要と合わせて参照。 --- ## 1. Phase 1: リクエスト送信 ### 1.1 Appboot デバイス登録と `nfvdid` Cookie の発行。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL Path | `{BaseESN}` | デバイス Base ESN (`NFANDROID1-PRV-P-L3-...`) | | URL Query | `keyVersion` | デバイス鍵バージョン (`1`) | | URL Query | `suspended` | アカウント停止フラグ (`false`) | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Header | `Content-Type` | `application/x-msl+json` | | Header | `x-netflix-deviceidtoken` | デバイス ID トークン (324 bytes) | | Header | `x-netflix.nfstatus` | Netflix ステータスコード (`1_1`) | | Cookie | `nfvdid` | デバイス ID Cookie (更新) | | Body | — | MSL JSON ペイロード | > `x-netflix-deviceidtoken` は appboot レスポンスで発行されるが、後続の HTTP リクエストで送信される様子は観測されていない。MSL ペイロード内部またはローカル保持と推定される。 --- ### 1.2 getProxyEsn (MSL) 新しい PXA ESN をサーバーから取得する。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/nq/androidui/samurai/~9.0.0/api` | | Body (MSL) | `url` | `/getProxyEsn` | **レスポンス (MSL 復号後):** | 場所 | フィールド | 説明 | |---|---|---| | Body | `result.esn` | 新しい PXA ESN (`NFANDROID1-PXA-P-L3-...`) | | Body | `serverTime` | サーバータイムスタンプ | | Body | `from` | レスポンス元 (`playapi`) | > レスポンスは `proxyEsn.response` イベントで取得。アプリは `result.esn` を `proxyEsn.onKnown` でキャッシュに保存する。 --- ### 1.3 aleProvision #1 (MSL) ALE (Application Level Encryption) のための RSA 鍵交換。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/nq/androidui/samurai/~9.0.0/api` | | Body (MSL) | `url` | `/aleProvision` | | Body (MSL) | `params.provisionRequest.keyx.scheme` | `RSA-OAEP-256` — 鍵交換アルゴリズム | | Body (MSL) | `params.provisionRequest.keyx.data.pubkey` | RSA 公開鍵 (2048-bit, Base64) | | Body (MSL) | `params.provisionRequest.scheme` | `A128GCM` — セッション暗号化方式 | | Body (MSL) | `params.provisionRequest.type` | `SOCKETROUTER` — プロビジョニング種別 | | Body (MSL) | `params.netflixClientPlatform` | `androidNative` | | Body (MSL) | `params.appVer` | `63928` | | Body (MSL) | `params.mId` | `GOOGLPIXEL=4A==5G=S` — Model ID | | Body (MSL) | `params.ffbc` | `phone` — Form Factor | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (A128GCM セッション鍵を RSA-OAEP-256 で暗号化して返すと推定) | --- ### 1.4 RenewSSOToken (MSL → GraphQL) SSO トークンの更新。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.cloud` → `/graphql` | | Body (MSL) | `operationName` | `RenewSSOToken` | | Body (MSL) | `variables.ssoToken` | 既存の SSO トークン (`BgiHtuvcAxL4AYwX...`) | | Body (MSL) | `extensions.persistedQuery.id` | `a4d00303-b02d-47c9-a53f-776b6a63b001` | | Body (MSL) | `extensions.persistedQuery.version` | `102` | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (SSO トークン更新結果) | > `ssoToken` はこの API でのみ使用される。他の API で再利用される様子は観測されていない。 --- ### 1.5 CurrentCountryQuery 国情報取得と認証 Cookie の発行。Non-MSL GraphQL。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/graphql` | | Cookie | `nfvdid` | デバイス識別子 | | Body | — | GraphQL JSON (平文) | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Header | `Content-Type` | `application/json;charset=UTF-8` | | Cookie | `NetflixId` | ユーザー認証 Cookie (新規発行) | | Cookie | `SecureNetflixId` | セキュア認証 Cookie (HTTPS only, 新規発行) | | Body | — | 国情報 JSON | > 起動フローで最初に `NetflixId` / `SecureNetflixId` Cookie を発行するレスポンス。 --- ### 1.6 InterstitialForProfileGate / InterstitialForLolomo (MSL → GraphQL) プロフィール選択画面・ホーム画面のインタースティシャル (中間画面) チェック。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.cloud` → `/graphql` | | Body (MSL) | `operationName` | `InterstitialForProfileGate` / `InterstitialForLolomo` | | Body (MSL) | `variables.format` | `HTML` | | Body (MSL) | `variables.resolutionMode` | `ANDROID_XHDPI` | | Body (MSL) | `variables.imageFormat` | `PNG` | | Body (MSL) | `variables.commonParameters.isConsumptionOnly` | `true` | | Body (MSL) | `extensions.persistedQuery.id` | `18f3ae27-a0f1-45c9-88e6-c6bd39159ecb` (ProfileGate) | | Body (MSL) | `extensions.persistedQuery.version` | `102` | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (インタースティシャル表示の要否) | --- ## 2. Phase 3: 追加リクエスト ### 2.1 AccountQuery (MSL → GraphQL) アカウント基本情報の取得。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.cloud` → `/graphql` | | Body (MSL) | `operationName` | `AccountQuery` | | Body (MSL) | `extensions.persistedQuery.id` | `4043dd89-0ed5-4d7f-ac5c-40c7ffcec7ae` | | Body (MSL) | `extensions.persistedQuery.version` | `102` | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (アカウント情報: プラン、プロフィール等) | --- ### 2.2 aleProvision #2 (MSL) 再鍵交換。#1 と同一の RSA 公開鍵を使用。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/nq/androidui/samurai/~9.0.0/api` | | Body (MSL) | — | aleProvision #1 と同一構造 (同一 RSA pubkey) | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (新セッション鍵 + PXA ESN) | > このレスポンスの直後に `proxyEsn.response` → `proxyEsn.onKnown` で新 PXA ESN が確定・キャッシュされる。 --- ### 2.3 PromoProfileGateVideoDataQuery プロフィール画面データの取得。Non-MSL GraphQL。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/graphql` | | Cookie | `nfvdid` | デバイス識別子 | | Cookie | `NetflixId` | ユーザー認証 Cookie | | Cookie | `SecureNetflixId` | セキュア認証 Cookie | | Body | — | GraphQL JSON (平文) | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Header | `Content-Type` | `application/json;charset=UTF-8` | | Cookie | `NetflixId` | ユーザー認証 Cookie (リフレッシュ) | | Cookie | `SecureNetflixId` | セキュア認証 Cookie (リフレッシュ) | | Body | — | プロフィール画面データ JSON | --- ## 3. Phase 4: 遅延リクエスト 起動から約 30 秒後にバックグラウンドで送信される。 ### 3.1 FetchConfigData (MSL → Samurai) デバイス・ストリーミング設定の一括取得。 **リクエスト:** | 場所 | フィールド | 説明 | |---|---|---| | URL | エンドポイント | `prod.ftl` → `/nq/androidui/samurai/v1/config` | | Body (MSL) | `method` | `get` | | Body (MSL) | `path` | `["deviceConfig"]`, `["hendrixConfig"]`, `["networkScoreConfig"]`, `["accountConfig"]` 等 | | Body (MSL) | `appType` | `samurai` | **レスポンス:** | 場所 | フィールド | 説明 | |---|---|---| | Body | — | MSL 暗号化 (設定データ) | --- ### 3.2 AccountQuery #2 (MSL → GraphQL) Phase 3 の AccountQuery と同一。キャッシュ更新のための再取得。 --- ## 4. GraphQL Persisted Query パターン Netflix Android は GraphQL の **Persisted Query** を使用する。クエリ本文は送信せず、事前登録済みの ID のみを指定する。 ### 4.1 確認済みクエリ一覧 | operationName | persistedQuery ID | version | プロトコル | エンドポイント | |---|---|---|---|---| | `RenewSSOToken` | `a4d00303-b02d-47c9-a53f-776b6a63b001` | 102 | MSL | prod.cloud | | `InterstitialForProfileGate` | `18f3ae27-a0f1-45c9-88e6-c6bd39159ecb` | 102 | MSL | prod.cloud | | `InterstitialForLolomo` | *(未取得)* | 102 | MSL | prod.cloud | | `AccountQuery` | `4043dd89-0ed5-4d7f-ac5c-40c7ffcec7ae` | 102 | MSL | prod.cloud | | `CurrentCountryQuery` | *(未取得)* | — | non-MSL | prod.ftl | | `PromoProfileGateVideoDataQuery` | *(未取得)* | — | non-MSL | prod.ftl | ### 4.2 MSL GraphQL vs Non-MSL GraphQL | 特性 | MSL GraphQL | Non-MSL GraphQL | |---|---|---| | エンドポイント | `prod.cloud/graphql` | `prod.ftl/graphql` | | ボディ暗号化 | MSL (CBOR → GZIP → JSON) | なし (平文 JSON) | | 認証 | MSL Master Token | `NetflixId` / `SecureNetflixId` Cookie | | レスポンス Cookie | `nfvdid` のみ | `nfvdid` + `NetflixId` + `SecureNetflixId` | --- ## 5. 完全タイムライン | # | イベント | エンドポイント | API / 操作 | 備考 | |---|---|---|---|---| | 1 | `proxyEsn.forceExpired` | — | PXA ESN 期限チェック | Frida が強制失効 | | 2 | `http.request` | appboot | `POST /appboot/{BaseESN}` | `keyVersion=1` | | 3 | `proxyEsn.requestHeaders` | — | ヘッダー準備 | | | 4 | `proxyEsn.request` | — | `/getProxyEsn` | | | 5 | `msl.api` | prod.ftl | `/getProxyEsn` | MSL 平文 | | 6 | `http.request` | prod.ftl | `samurai/~9.0.0/api` | MSL 暗号文 | | 7 | `msl.api` | prod.ftl | `/aleProvision` #1 | RSA-OAEP-256 | | 8 | `http.request` | prod.ftl | `samurai/~9.0.0/api` | MSL 暗号文 | | 9 | `msl.api` | prod.cloud | `RenewSSOToken` | GraphQL Mutation | | 10 | `http.request` | prod.cloud | `/graphql` | MSL 暗号文 | | 11 | `http.request` | prod.ftl | `/graphql` | CurrentCountryQuery | | 12 | `msl.api` | prod.cloud | `InterstitialForProfileGate` | | | 13 | `msl.api` | prod.cloud | `InterstitialForLolomo` | | | 14 | `http.request` | prod.cloud | `/graphql` | MSL 暗号文 | | 15 | `http.request` | prod.cloud | `/graphql` | MSL 暗号文 | | 16 | `http.response` | prod.ftl | CurrentCountryQuery | **+NetflixId [Cookie]** | | 17 | `http.response` | prod.ftl | getProxyEsn | | | 18 | `http.response` | prod.ftl | aleProvision #1 | | | 19 | `msl.api` | prod.cloud | `AccountQuery` | | | 20 | `http.request` | prod.cloud | `/graphql` | MSL 暗号文 | | 21 | `http.response` | appboot | appboot | **+nfvdid [Cookie]** + DeviceIdToken | | 22 | `http.response` | prod.cloud | RenewSSOToken | | | 23 | `http.response` | prod.cloud | InterstitialForLolomo | | | 24 | `http.response` | prod.cloud | InterstitialForProfileGate | | | 25 | `http.response` | prod.cloud | AccountQuery | | | 26 | `msl.api` | prod.ftl | `/aleProvision` #2 | 同一 RSA pubkey | | 27 | `http.request` | prod.ftl | `samurai/~9.0.0/api` | MSL 暗号文 | | 28 | `http.response` | prod.ftl | aleProvision #2 | | | 29 | `proxyEsn.response` | — | 新 PXA ESN 受信 | | | 30 | `proxyEsn.onKnown` | — | ESN キャッシュ保存 | `sn=8572399748193023` | | 31 | `http.request` | prod.ftl | `/graphql` | PromoProfileGate | | 32 | `http.response` | prod.ftl | PromoProfileGate | +NetflixId [Cookie] | | 33 | `msl.api` | prod.ftl | FetchConfigData | Samurai config | | 34 | `msl.api` | prod.cloud | `AccountQuery` #2 | | | 35 | `http.request` | prod.ftl | `samurai/v1/config` | MSL 暗号文 | | 36 | `http.request` | prod.cloud | `/graphql` | MSL 暗号文 | | 37 | `http.response` | prod.cloud | AccountQuery #2 | | | 38 | `http.response` | prod.ftl | FetchConfigData | |