【Django】Apple MusicKit JS で Apple Music サインインの問題発生

事象

まず、MusicKit JS でプレイヤーを設置し曲をフル再生しようとすると Apple Music へのサインインが必要となります。

その際の正常なフローとしては:

  1. 自動ポップアップでサインイン画面が表示される
  2. ユーザーID & パスワード、そして6 桁のワンタイムコードを入力してサインイン
    • Mac の Safari では指紋認証のみでサインイン可
  3. アプリケーション(オリジン毎)による Apple Music へのアクセス許可を求められる
  4. 「許可」ボタンのクリックするとポップアップ画面が閉じる
  5. アプリケーションで曲のフルバージョンが再生できるようになる

今回、上記 4 の「許可」ボタンをクリックしてもボタンがグレーアウトされるだけで何も変わらない事象が起きた。

ポップアップウィンドウは閉じず、曲も再生されない。

もう一度曲を再生しようとしても再度別のポップアップが開いてサインインを要求されるという状況でした。

ちなみにサインインされてない状態だとフルバージョンではなく 30 秒バージョンの再生になります。

問題 1:「許可」ボタンクリックから進まない

まず、上記ステップで「許可」ボタンをクリックしてもボタンがグレーアウトするだけでその先に進まなかった問題から。

原因:Referrer-Policy の設定漏れ

諸々調べた結果、原因としてボタンクリック時の HTTP request に Referer 情報が含まれていなかった事が挙げられます。

Django ではデフォルトの Referrer-Policy が same-origin となりますが、same-origin の挙動は「同一オリジンのリクエストではオリジン、パス、クエリー文字列を送信します。オリジン間リクエストでは Referer ヘッダーを送信しません。」となっています。

つまり Apple の認証システムへの request 時に Referer ヘッダーが送信されず、その後の処理が行われなかったと思われます。

Django での Referrer-Policy 変更方法

Django で Referrer-Policy を変更するには settings.py で SECURE_REFERRER_POLICY を設定する必要があります。

おそらく「SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"」で良いと思います。

残る問題

これで「許可」ボタンをクリックすると、ポップアップウィンドウの中でアプリが表示されました。

仕方なくその中で曲を再生してみようとするとまた別のポップアップウィンドウが開きサインインを求められるループに入りました。

一旦グレーアウトで止まる問題は解消されましたが、別の問題が発生しただけです。

問題 2:曲のフル再生ができない

「許可」ボタンクリック後ポップアップウィンドウが閉じない問題はさておき、曲が再生できないのはなぜなのかを調べました。

原因 1/2:musicUserToken が付与されない

デベロッパーツールで request/response の内容を確認した結果、「許可」ボタンをクリックした際の Apple 側からの response で musicUserToken を受け取っているにもかかわらず、それがこちら側の musicKitInstance に渡っていないことが判明。

正常時はブラウザの Local Storage に保存され、無事認証が完了した musicKitInstance をデベロッパーツールで覗くと下記のように musicUserToken が入っているはずですが、今回はこれが「undefined」となっていました。

ちなみにコード上で半ば無理やり下記のようにアサインすると曲の再生ができていました。(music は musicKitInstance)

music.musicUserToken = "AoSKv/b0ED6YZzVuAEIvki4eOgFgeQYrCPaU+KSFV7fFdEozGUawOuKXxrzGyISRlHPfJlOzkclA+Nk4I0SbLI/f0tiZ++a+QYOG3EP+d935PvL+udndhJjfG/xe+ctry69X/rTtqgdr2VRCbqMgt/xzocg7gg2w/QPuTcA7YSpevglys3/2AsC69ofZKl8fHKkp04dyLuhxVZOC2h4PGXc+6chmnSHIxo7tp/VTv+IWr8+fhQ=="

原因 2/2:ポップアップ起動直前の URL が「#」で終わっている

正直かなり特殊な例かもしれませんが、自分のアプリ上、曲のタブを表示するボタンに a タグを使用し href="#" としていました。

つまり、曲を再生する前にこれをクリックするとメインウィンドウの URL が「http://localhost:8000/#」という形になりますが、これが良くなかったようです。

サインイン用のポップアップが開いて「許可」ボタンをクリックした後、ポップアップウィンドウの URL が更新されますが、その際メインウィンドウの URL と musicUserToken が「#」で繋がったものが入ります。

「http://localhost:8000/#ユーザートークン」の形で「#」につづけて musicUserToken が入ってくるんですが、上記で先に「#」が一つ入るため「http://localhost:8000/##ユーザートークン」という風に「#」が重複していました。

これが原因なのではと思い「#」を一つ消してブラウザ更新すると musicKitInstance に musicUserToken がアサインされ、URL からトークンの部分が消えました。(http://localhost:8000/ になる)

解決:URLに「#」を含まない様変更

アプリのコードを変更して「#」が付かないようにしたところ、「許可」ボタンクリックで musicUserToken は付与されるようになりました。

残る問題

ただ、メインウィンドウに戻らずポップアップ上にそのままアプリが表示される状況です。

その状態で曲はフル再生できますが、ポップアップが閉じてくれないのは問題です。

正直これに関しては解決できませんでした。

Django のテンプレートをそのまま Django の外に置いて python の http.server の Web サーバーで表示したところ無事にサインインできました。

なぜ内容が同じなのに Django で render されたものだとポップアップの挙動がおかしくなるのかは謎です。

と言うわけで、結局 html/css/js の部分は Django を通さず、API 部分だけ Django で書くことにしました。

それによってクロスオリジンのエラーを解決する必要がありましたが今のところ大丈夫そうです。

その他メモ書き

  • musicUserToken はオリジン毎の割り当て。
    • 127.0.0.1:8000 で取得した musicUserToken は localhost:8000 には共有されない。
    • 同じ http://127.0.0.1:8000 であれば http.server を使った html/js でも Django の runserver を使った Django テンプレートでも同じ musicUserToken で動く。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です