Help us understand the problem. What is going on with this article?

Spotify API の簡単な使い方-ログイン機能-

RailsとJsでSpotifyAPIをたたいてみる

環境・前提条件

ruby 2.6.2
rails 5.2.4

この記事に書いてあること

  • ユーザーを認証し、ユーザーデータにアクセスするための承認を取得する
  • Web APIエンドポイントからデータを取得する。 下記のURLはSpotifyAPIの公式ドキュメントです。公式ドキュメントとやっていることは同じですが、この記事ではわかりやすく進行していきます。

参考URL

公式ドキュメント
https://developer.spotify.com/documentation/web-api/quick-start/
公式github
https://github.com/spotify/web-api-auth-examples

この記事でできること

アプリのログイン→Spotifyログイン

giitaよう.gif

対象者

  • Spotify APIをたたきたいけど、どうやってたたけばいいか分からないAPI初心者
  • ドキュメント読むの難しいと感じている方
  • Spotify会員
  • deviseを使ってログイン機能が作成できる方

今回取得するトークンについて❶

例えばWEB PLAYBACK SDKを使いたいと思ったとき、
下記のソースコードをコピペすればプレイヤーを使用できます。音も鳴るよ♬
ただ参考URLで取得したトークンだと1時間で切れてしまうので参考URLで取得したAccessTokenのところを1時間に一回手動で書き換えなくてはいけません。そんなの無理ー(⌒-⌒; )って感じですよね!
そこで登場するのが今回取得するトークンなんです。

index.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Spotify Web Playback SDK Quick Start Tutorial</title>
</head>
<body>
  <h1>Spotify Web Playback SDK Quick Start Tutorial</h1>
  <h2>Open your console log: <code>View > Developer > JavaScript Console</code></h2>

  <script src="https://sdk.scdn.co/spotify-player.js"></script>
  <script>
    window.onSpotifyWebPlaybackSDKReady = () => {
      const token = '参考URLで取得したAccessToken';
      const player = new Spotify.Player({
        name: 'Web Playback SDK Quick Start Player',
        getOAuthToken: cb => { cb(token); }
      });

      // Error handling
      player.addListener('initialization_error', ({ message }) => { console.error(message); });
      player.addListener('authentication_error', ({ message }) => { console.error(message); });
      player.addListener('account_error', ({ message }) => { console.error(message); });
      player.addListener('playback_error', ({ message }) => { console.error(message); });

      // Playback status updates
      player.addListener('player_state_changed', state => { console.log(state); });

      // Ready
      player.addListener('ready', ({ device_id }) => {
        console.log('Ready with Device ID', device_id);
      });

      // Not Ready
      player.addListener('not_ready', ({ device_id }) => {
        console.log('Device ID has gone offline', device_id);
      });

      // Connect to the player!
      player.connect();
    };
  </script>
</body>
</html>

今回取得するトークンについて❷

Spotifyが用意しているトークンの種類は3種類あります。
下記公式ドキュメントですので、目を通してみて下さい。
https://developer.spotify.com/documentation/general/guides/authorization-guide/

今回取得するものは上記のURLの一番下のものです。
Client Credentials Flowは時間が経つと切れてしましますが、手動でコードを直さなくてもいいような仕様となります。3種類の中でも一番簡単でお手軽ですね♬

手順❶

Spotify API公式ドキュメントのDASHBOADからアプリを作成し、
ClientID,SecretIDを取得しましょう。
スクリーンショット 2019-12-06 13.22.43.png
スクリーンショット 2019-12-06 13.25.26.png

アプリを作成したらClientID,SecretIDを確認しましょう。
次に、ページ右にあるEDIT SETTINGからRedirect URIsを登録しよう。
スクリーンショット 2019-12-06 13.29.11.png
スクリーンショット 2019-12-06 13.34.45.png

手順❷

deviseを使ってログイン画面を作る。
おなじみdevise先生です♬

Gemfile
gem 'devise'

$ bundle install
$ rails g devise:install

$ rails g devise User
$ rails db:migrate
スクリーンショット 2019-12-05 12.30.58.png

application.html.erb
<!DOCTYPE html>
<html>

<head>
  <title>Authorization</title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= include_gon %>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>

<body>
  <% if user_signed_in? %>
  <ul class="navbar">
    <li class="nav-item item">
      <%= link_to "Log out", destroy_user_session_path, method: :delete, class:"nav-link" %>
    </li>
  </ul>
  <% else %>
  <ul class="navbar">
    <li class="nav-item item">
      <%= link_to "Sign up", new_user_registration_path, class:"nav-link" %>
    </li>
    <li class="nav-item item">
      <%= link_to "Log in", new_user_session_path, class:"nav-link" %>
    </li>
  </ul>
  <% end %>
  <%= yield %>
</body>

</html>
application_controller.rb
class ApplicationController < ActionController::Base
    def after_sign_in_path_for(resource)
        if user_signed_in?
            spotify_login_path
        end    
    end
    def after_sign_out_path_for(resource)
        new_user_session_path
    end        
end

手順❸

APIキーを管理しましょう。

Gemfile
gem 'gon'
gem 'dotenv-rails'

*補足:gonはrailsのコントローラーで指定した変数をJsに持っていける便利gemです♬
    dotenv-railsは.envファイルを管理するgemです♬

$ bundle install

$ rails g controller users

users_controller.rb
class UsersController < ApplicationController
    def spotify_login
        gon.client_id = ENV['SPOTIFY_CLIENT_ID'];
        gon.redirect_uri = ENV["SPOTIFY_REDIRECT_URI"];
    end
end

APIキーは環境変数にして、
.envファイルを作成し、その中に入れましょう。

.env
SPOTIFY_CLIENT_ID=your client ID
SPOTIFY_SECRET_ID=your secret ID
SPOTIFY_REDIRECT_URI=http://localhost:3000/tests/
spotify_login.html.erb
<!doctype html>
<html>

<head>
    <title>Example of the Implicit Grant flow with Spotify</title>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    <style type="text/css">
        #login,
        #loggedin {
            display: none;
        }

        .text-overflow {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            width: 500px;
        }
    </style>
</head>

<body>
    <div class="gradation">
        <div class="container">
            <div id="login">
                <br>
                <br>
                <h1>This is Spotify Authentication</h1>
                <div class="text-center"><button id="login-button" class="btn btn-primary">Log in with Spotify</button>
                </div>
            </div>
            <div id="loggedin">
                <div id="user-profile">
                </div>
                <div id="oauth">
                </div>
            </div>
        </div>
    </div>
    <script id="user-profile-template" type="text/x-handlebars-template">
        <h1>Logged in as {{display_name}}</h1>
        <div class="media">
        <div class="pull-left">
            <img class="media-object" width="150" src="{{images.0.url}}" />
        </div>
        <div class="media-body">
            <dl class="dl-horizontal">
            <dt>Display name</dt><dd class="clearfix">{{display_name}}</dd>
            <dt>Id</dt><dd>{{id}}</dd>
            <dt>Email</dt><dd>{{email}}</dd>
            <dt>Spotify URI</dt><dd><a href="{{external_urls.spotify}}">{{external_urls.spotify}}</a></dd>
            <dt>Link</dt><dd><a href="{{href}}">{{href}}</a></dd>
            <dt>Profile Image</dt><dd class="clearfix"><a href="{{images.0.url}}">{{images.0.url}}</a></dd>
            <dt>Country</dt><dd>{{country}}</dd>
            </dl>
        </div>
        </div>

    </script>
    <script id="oauth-template" type="text/x-handlebars-template">
        <h2>oAuth info</h2>
        <dl class="dl-horizontal">
        <dt>Access token</dt><dd class="text-overflow">{{access_token}}</dd>
        </dl>
    </script>

    <script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0-alpha.1/handlebars.min.js"></script>
    <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>

    <script>
        (function () {

            var stateKey = 'spotify_auth_state';

            /**
             * Obtains parameters from the hash of the URL
             * @return Object
             */
            function getHashParams() {
                var hashParams = {};
                var e, r = /([^&;=]+)=?([^&;]*)/g, q = window.location.hash.substring(1);
                while (e = r.exec(q)) {
                    hashParams[e[1]] = decodeURIComponent(e[2]);
                }
                return hashParams;
            }

            /**
             * Generates a random string containing numbers and letters
             * @param  {number} length The length of the string
             * @return {string} The generated string
             */
            function generateRandomString(length) {
                var text = '';
                var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

                for (var i = 0; i < length; i++) {
                    text += possible.charAt(Math.floor(Math.random() * possible.length));
                }
                return text;
            };

            var userProfileSource = document.getElementById('user-profile-template').innerHTML,
                userProfileTemplate = Handlebars.compile(userProfileSource),
                userProfilePlaceholder = document.getElementById('user-profile');

            oauthSource = document.getElementById('oauth-template').innerHTML,
                oauthTemplate = Handlebars.compile(oauthSource),
                oauthPlaceholder = document.getElementById('oauth');

            var params = getHashParams();

            var access_token = params.access_token,
                state = params.state,
                storedState = localStorage.getItem(stateKey);
            console.log("-------------------------")
            console.log(access_token);
            if (access_token && (state == null || state !== storedState)) {
                alert('There was an error during the authentication');
            } else {
                localStorage.removeItem(stateKey);
                if (access_token) {
                    $.ajax({
                        url: 'https://api.spotify.com/v1/me',
                        headers: {
                            'Authorization': 'Bearer ' + access_token
                        },
                        success: function (response) {
                            userProfilePlaceholder.innerHTML = userProfileTemplate(response);
                            console.log(response);
                            $('#login').hide();
                            $('#loggedin').show();
                        }
                    });
                } else {
                    $('#login').show();
                    $('#loggedin').hide();
                }

                document.getElementById('login-button').addEventListener('click', function () {

                    var client_id = gon.client_id; // Your client id
                    var redirect_uri = gon.redirect_uri; // Your redirect uri

                    var state = generateRandomString(16);

                    localStorage.setItem(stateKey, state);
                    var scope = 'streaming user-read-private user-read-email playlist-modify-private';

                    var url = 'https://accounts.spotify.com/authorize';
                    url += '?response_type=token';
                    url += '&client_id=' + encodeURIComponent(client_id);
                    url += '&scope=' + encodeURIComponent(scope);
                    url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
                    url += '&state=' + encodeURIComponent(state);

                    window.location = url;
                }, false);
            }
        })();
    </script>

</html>

手順❹

ログイン後のviewを作成しましょう。
$ rails g conrtoller tests

tests_controller.rb
class TestsController < ApplicationController
    def index
    end
end
index.html.erb
ログインできました。
お疲れ様でした!
routes.rb
Rails.application.routes.draw do
  devise_for :users
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  get 'users/spotify_login', to: 'users#spotify_login', as: 'spotify_login'
  resources :tests, only: [:index]
end

手順❺

完成!

ログインしてみましょう。
access_tokenは遷移先(今回でいうとtests_path)のURLに表示されています。
URLのxxxのところがaccess_tokenです。
expires_in=3600は、トークンの期限を表示しています。3600秒=1時間です。1時間でaccess_tokenが切れるのでユーザーは1時間ごとにログインをし直しましょう♬
http://localhost:3000/tests/#access_token=xxx&token_type=Bearer&expires_in=3600&state=caGLGQ8xzz1yYbH7

Github

全コードは下記のgithubを参照ください♬
https://github.com/natan777natan/authorization

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away