Spotify Web APIのApp作成とアクセストークンの取得 ― ReactでWebアプリ開発〈3〉

はじめに

前回の記事↓

Next.jsにdaisyUIを導入する(サンプルページ付き) ― ReactでWebアプリ開発〈2〉

開発予定のWebアプリ内でSpotifyが提供するWeb APIを利用しようと考えているので、一度、試しに使ってみることにしました。利用するのは初めてなので、今回の記事では、公式ドキュメントに従ってApp作成からアクセストークンの取得までを行ってみます。

準備

APIを使うためには、開発者ページで自分のAppを作成する必要があります。

まず以下のサイトにアクセスし、自分のSpotifyアカウントでログインします(freeまたはpremiumのSpotifyアカウントを持っている必要があります)。その後、右上のボタンからDashboardページに移動します。

Home | Spotify for Developers

Spotify for DevelopersのHomeページ

Dashboardには自分のAppの一覧が表示されています。まだAppが一つもないので、"create app"でAppを新規作成します。

Dashboardページ

App作成のためのフォームに移動するので、App nameやApp description、Redirect URIsといった必須項目を入力します。

ちなみに、Redirect URIsは認証ページでユーザーがログインした後にリダイレクトできるページです。ここに登録されているページ以外にリダイレクトしようとするとエラーになります。後から変更可能なので、開発段階ではhttp://localhost:3000/callbackなどを登録しておけば良いと思います。(ユーザーの認証が必要でない場合、特に利用しないです。詳細は、この後の認可フローの説明でまた出てきます。)

App作成フォームページ

これでAppの作成は完了です。作成したAppのホームに移動すると、アクティブユーザー数などの様々な情報を見ることができます。また、Settingsボタンを押して設定のページに移動すると、"Client ID"と"Client secret"が確認できます。これらは次章のアクセストークンの取得で使用します。

App作成フォームページ

アクセストークンの取得

SpotifyのWeb APIを呼び出す際は、常にアクセストークンをヘッダに含める必要があります。もし、アクセストークン無しでAPIを呼び出すと、以下のようなエラーが返ってきます。

{
    "error": {
        "status": 401,
        "message": "No token provided"
    }
}

アクセストークンの取得方法は、APIから取得したいデータの種類によって大きく以下の2つに分けられます。

  • Client Credentials Flow(ユーザの認可不要)
  • Authorization Code Flow(ユーザの認可必要)

(ちなみにこのようなAPIの認可フローはOAuth 2.0という標準プロトコルに沿って実装されているそうです。OAuthにつては、コチラのページが理解の助けになりました。 → 一番分かりやすい OAuth の説明 | Qiita)。

Client Credentials Flow

ユーザの認可(authorization)を必要としない認可フローです。手軽ですが、ユーザ情報にはアクセスできず、ユーザのライブラリを操作したりすることもできません。Spotifyから曲やアルバムのデータだけを取得したい場合に使用します。

フローの流れは以下の図のようになります。

Client Credentials Flowの図
出典:Client Credentials Flow | Spotify for Developers

フローの詳細

まず、先ほど取得した、AppのClient IDとClient secret、そして認可フローの方式を指定するgrant_typeを含んだHTTPリクエストを、アクセストークン取得用のエンドポイント(https://accounts.spotify.com/api/token)に送ります。(図中①)。

HTTPリクエストの詳細は以下の表の通りです。

項目
メソッド POST
ヘッダ Authorization: Basic <base64 encoded client_id:client_secret>
Content-Type: application/x-www-form-urlencoded
ボディ grant_type=client_credentials

Client IDとClient secretの渡し方が少し複雑です。まず、これらを":"でつないだ文字列client_id:client_secretを用意し、それをBase64という方式でエンコードした上でAuthorizationヘッダに埋め込みます。

ボディ(本体)には、認可フローの方式を指定したgrant_typeパラメータを含めます。Content-Typeヘッダで指定した通り、application/x-www-form-urlencoded形式(URLパラメータと同じkey1=value1&key2=value2のような形式)である必要があります。

レスポンスはJSON形式で返ってきます。含まれている情報は以下の表の通りです。

キー 説明
access_token 取得したアクセストークン。
token_type トークンタイプ。常に"Bearer"。
expires_in アクセストークンの有効時間(秒)。3600秒(1時間)。

これでアクセストークンの取得は完了です。以後、Web APIを呼び出す度に、ヘッダにAuthorization: Bearer <access_token>という形式でアクセストークンを含めることで、目的の情報を得ることができます。(図中②)

Node.js上で実行

以上の内容をjavascriptコードで書くと、以下のようになります。例として、Syd Barrettのアーティスト情報について取得してみました。

client_credentials_flow.js
const client_id = process.env.CLIENT_ID;
const client_secret = process.env.CLIENT_SECRET;

// アクセストークンの取得(1)
async function getAccessToken() {
    const response = await fetch('https://accounts.spotify.com/api/token', {
        method: 'POST',
        headers: {
            'Authorization': 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64'),
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
            'grant_type': 'client_credentials'
        })
    });

    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data.access_token;
}

// Spotify Web APIからデータ取得(2)
async function getSpotifyData() {
    try {
        const token = await getAccessToken();

        // Spotify Web APIへのリクエストを送る
        // (例:Syd Barrett [artist ID: 6Lt3HS8R2v8Q4G7ZkUWa8R] のアーティスト情報を取得)
        const response = await fetch('https://api.spotify.com/v1/artists/6Lt3HS8R2v8Q4G7ZkUWa8R', {
            headers: {
                'Authorization': 'Bearer ' + token
            }
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

getSpotifyData();

node.js用に書いているので、

node --env-file=.env client_credentials_flow.js

で簡単に実行できます(クライアントIDなどは.envファイルにまとめているので、オプションで指定しています。dotenvライブラリを使う方法もあります → Node.js の進化に伴い不要となったかもしれないパッケージたち | Zenn)。fetch()も追加ライブラリ無しで使えるようになっていて便利です。

レスポンスとして返ってきたJSONデータの出力結果はこんな感じです。(popularity: 38は納得いかないですね…)

{
  external_urls: { spotify: 'https://open.spotify.com/artist/6Lt3HS8R2v8Q4G7ZkUWa8R' },
  followers: { href: null, total: 345500 },
  genres: [
    'art rock',
    'experimental',
    'outsider',
    'psychedelic folk',
    'psychedelic rock'
  ],
  href: 'https://api.spotify.com/v1/artists/6Lt3HS8R2v8Q4G7ZkUWa8R?locale=*',
  id: '6Lt3HS8R2v8Q4G7ZkUWa8R',
  images: [
    {
      url: 'https://i.scdn.co/image/f0cded2845369a436adb00b24c85d02b867d0f45',
      height: 300,
      width: 300
    },
    {
      url: 'https://i.scdn.co/image/9be66078df9f6c16d5f7bd09c08d0eb9b2b20cd6',
      height: 200,
      width: 200
    },
    {
      url: 'https://i.scdn.co/image/877b78cc2a8ff5c592d253deca15f9d912071e04',
      height: 64,
      width: 64
    }
  ],
  name: 'Syd Barrett',
  popularity: 38,
  type: 'artist',
  uri: 'spotify:artist:6Lt3HS8R2v8Q4G7ZkUWa8R'
}

(ちなみに、先ほどのjsコードはほとんどChatGPTに出力させたものです。公式ページのURLを渡し、「ページの内容をnode.js用のjavascriptコードにしてください。」とお願いすると、この精度で出力してくれました。すごい。)

Authorization Code Flow

ユーザの認可が必要な認可フローです。ユーザの情報にアクセスしたい場合や、ユーザのライブラリを操作したい場合に使用します。認可を求める際には、スコープ(アクセス可能な範囲や操作権限)を設定します。使用例として、以下のような場合が挙げられます(かっこ内が必要なスコープです。公式ドキュメントで確認できます。)。

  • ユーザが保存したアルバムを取得する(user-library-read
  • 新しい非公開プレイリストを作成する(playlist-modify-private
  • ユーザのライブラリに曲を保存する(user-library-modify

フローの流れは以下の図のようになります。

Authorization Code Flowの図
出典:Authorization Code Flow | Spotify for Developers

フローの詳細

まず、アクセストークン取得の前にユーザの認可が必要なため、認可用のエンドポイント(https://accounts.spotify.com/authorize)にリクエストを送る必要があります。(図中①)

メソッドにはGETを使い、必要な情報はクエリ文字列(URLパラメータ)で渡します。パラメータの種類は以下の表のとおりです。

キー 説明 タイプ
client_id クライアントID。 必須
response_type codeに設定。 必須
redirect_uri ユーザの認可後にリダイレクトするURI。App設定のRedirect URIsに登録されている必要がある(前述)。 必須
state クロスサイトリクエストフォージェリという攻撃を防ぐためのもの。ランダムに生成した文字列など。 任意(強く推奨)
scope 認可を求めるスコープ。複数の場合は空白区切り。 任意
show_dialog ユーザが既にアプリを許可済みの場合にもう一度許可を求めるか。既定値はfalse。 任意

実際にリクエストを送ってみます。WebのSpotifyからログアウトした状態で、ブラウザ上でhttps://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:3000/callback&scope=user-library-modify"にアクセスしてみます。

すると、まずログイン画面が現れます(認証, Authentication)。

ログイン画面

ここでログインすると、ユーザの許可を求める画面になります(認可, Authorization)。

同意確認画面

同意、あるいはキャンセルした後、redirect_uriに指定したページにリダイレクトされます。

同意した場合、アクセストークン取得に必要なcode(リクエストに設定した場合、stateも)が返ってきます。これらの値は、リダイレクトされたURLのクエリ文字列に含まれています。(ちなみに、サーバを立ち上げていないので、サイトの方にはアクセスできていません)

同意後のリダイレクト画面

ユーザがキャンセルした場合など、認可が失敗した場合はerror(とstate)が返ってきます。

キャンセル後のリダイレクト画面

ユーザが同意して、無事codeが取得できたら、それを用いてアクセストークンを取得します。Client Credentials Flowと同様に、アクセストークン取得用のエンドポイント(https://accounts.spotify.com/api/token)にHTTPリクエストを送ります(図中②)。

ただし、ボディに含める情報は多くなっています。codeパラメータに取得したcodeを渡し、redirect_uriパラメータには認可リクエストを送った際に渡したredirect_uriをそのまま渡します(検証用だそうで、必ず一致している必要があります)。具体的には以下の表の通りです。

項目
メソッド POST
ヘッダ Authorization: Basic <base64 encoded client_id:client_secret>
Content-Type: application/x-www-form-urlencoded
ボディ grant_type=authorization_code&
code=<authorization code>&
redirect_uri=<redirect uri>

レスポンスはJSON形式で返ってきます。含まれている情報は以下の表の通りです。

キー 説明
access_token 取得したアクセストークン。
token_type トークンタイプ。常に"Bearer"。
scope 認可されたスコープのリスト。(空白区切り)
expires_in アクセストークンの有効時間(秒)。3600秒(1時間)。
refresh_token 新しいアクセストークンの取得の際に使うリフレッシュトークン。

これでアクセストークンの取得は完了です。Client Credentials Flowと同様、Web APIを呼び出す度に、ヘッダにAuthorization: Bearer <access_token>という形式でアクセストークンを含めることで、目的の操作を行うことができます。(図中③)

また、アクセストークンは1時間で有効期限が切れますが、refresh_tokenを使用することで、再認可を求めずに新しいアクセストークンを取得することができます(図中④)。詳細は公式ドキュメントを参照してください(→ Refreshing tokens | Spotify for Developers)。

Node.js上で実行

以上の内容をjavascriptコードで書くと、以下のようになります。(例によって、ChatGPTに指示を出して生成させました。)

authorization_code_flow.js
const express = require('express');
const app = express();
const port = 3000;

// Spotify APIの設定
const client_id = process.env.CLIENT_ID; // SpotifyのクライアントID
const client_secret = process.env.CLIENT_SECRET; // Spotifyのクライアントシークレット
const redirect_uri = 'http://localhost:3000/callback'; // リダイレクトURI
const scope = 'user-library-modify'; // 必要なスコープ

// ログイン用URLを準備
app.get('/login', (req, res) => {
    const params = new URLSearchParams({
        response_type: 'code',
        client_id: client_id,
        scope: scope,
        redirect_uri: redirect_uri
    });
    // Spotifyの認可用エンドポイント(/authorize)へリクエストを送る (1)
    const authUrl = 'https://accounts.spotify.com/authorize?' + params.toString();
    res.redirect(authUrl);
});


// コールバックURLで認可コードを受け取り、アクセストークンを取得
app.get('/callback', async (req, res) => {
    // クエリ文字列内のcodeを取得
    const code = req.query.code;

    if (!code) {
        return res.status(400).send('No code provided');
    }

    try {
        // アクセストークンを取得 (2)
        const response = await fetch('https://accounts.spotify.com/api/token', {
            method: 'POST',
            headers: {
                'Authorization': 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64'),
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: code,
                redirect_uri: redirect_uri
            })
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        const { access_token, refresh_token, expires_in } = data;

        // アクセストークンを使ってSpotify APIにリクエストを送る (3)
        // ここではアクセストークンを表示するだけ
        res.send(`Access Token: ${access_token}<br>Refresh Token: ${refresh_token}<br>Expires In: ${expires_in}`);

    } catch (error) {
        console.error('Error fetching access token:', error);
        res.status(500).send('Error fetching access token');
    }
});

// サーバーを起動
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

サーバを立てる必要があるので、Node.jsのExpressを利用しています。必要な場合はインストールします。

npm install express

プログラムは、前章と同様に以下のコマンドで実行します。

node --env-file=.env authorization_code_flow.js

実行開始後、http://localhost:3000/loginにアクセスすると、先ほどのログイン画面や同意画面に移動します(ログイン済みの場合はスキップされます)。同意後、自動的にhttp://localhost:3000/callbackにリダイレクトされ、アクセストークンなどの情報が画面に表示されたら成功です。(ちなみに、stateパラメータに関しては省いていますが、実際の制作物ではセキュリティ的に使った方がいいと思われます。)

APIを通してライブラリに曲を保存する

せっかくアクセストークンを取得したので、何かユーザの情報に対して処理を行ってみようと思います。先ほどのコードではスコープをuser-library-modifyに設定しているので、APIを通して、ログインしたユーザのライブラリを変更することができます(曲の追加や削除など)。

ということで、ユーザが保存した曲のプレイリストである「お気に入りの曲」にトラックを一曲保存する処理をつけ足してみました。(注:実装を簡単にするために、グローバル変数を使ってアクセストークンを保管しています)

authorization_code_flow.js
...

// Spotify APIの設定
const client_id = process.env.CLIENT_ID; // SpotifyのクライアントID
const client_secret = process.env.CLIENT_SECRET; // Spotifyのクライアントシークレット
const redirect_uri = 'http://localhost:3000/callback'; // リダイレクトURI
const scope = 'user-library-modify'; // 必要なスコープ
let access_token; // アクセストークン

// (中略)

app.get('/callback', async (req, res) => {
    ...

    try {

        // (中略)

        const data = await response.json();
        access_token = data.access_token;  // アクセストークンを保存

        res.send(`Access token successfully retrieved. access_token: ${access_token}`);
    } catch (error) {
        console.error('Error fetching access token:', error);
        res.status(500).send('Error fetching access token');
    }
});

app.get('/save-track', async (req, res) => {
    if (!access_token) {
        return res.status(400).send('No token provided')
    }

    try {
        // アクセストークンを使ってSpotify APIにリクエストを送る (3)
        // (例:「お気に入りの曲」に曲 [track ID: 6hnoeJAhNt0TLaX5yzPr2l] を1曲追加)
        const response = await fetch('https://api.spotify.com/v1/me/tracks?ids=6hnoeJAhNt0TLaX5yzPr2l', {
            method: 'PUT',
            headers: {
                'Authorization': 'Bearer ' + access_token
            }
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        res.send('Track successfully saved');
    } catch (error) {
        console.error('Error:', error);
    }
});

// サーバーを起動
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

サーバの起動後、/loginでログインした後に、/save-trackにアクセスします。うまくいった場合、自分のSpotifyを開いてみると、プレイリスト「お気に入りの曲」の中に"Love You"という曲が追加されているはずです。

(コチラのAPIエンドポイントを利用しました。 → Save Tracks for Current User | Spotify for Developers

Development mode

ちなみに、他のアカウント(App作成元以外のアカウント)を用いてログインし、上記のプログラムを試してみたところ、うまくいきませんでした。どうやら、作成されたAppの初期状態はDevelopment modeになっており、その状態では操作できるアカウントは1つのみに限られているようです(App設定ページの"App Status"で確認できます)。

一応、Development modeでも、App設定ページの"User Management"欄に登録すれば、25人のユーザまでを操作できるようになります(ベータテスターや共同開発者を登録できます)。

UserManagement登録画面

本格的なアプリケーションとして、より多くのユーザが使用可能なものにするためには、Extended quota modeにする必要があります。申請と審査が必要だそうです(→ Quota modes | Spotify for Developers)。Extended quota modeはAPIリクエスト数の制限も緩和されるようで、制作物を公にリリースする場合はコチラを使った方が良さそうです。

おわりに

APIの認可フローの解読だけで結構時間がかかってしまいました…。次回、Spotify Web APIを使って、アルバムや曲の情報を取得してみようと思います。

次回の記事↓

Spotify Web APIで色んな情報を取得してみた ― ReactでWebアプリ開発〈4〉