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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/c0b8dac5-5300-7ac8-d1f9-5b74b11aeb89.gif)


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

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

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


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

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

###手順❶
Spotify API公式ドキュメントのDASHBOADからアプリを作成し、
ClientID,SecretIDを取得しましょう。
![スクリーンショット 2019-12-06 13.22.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/31916155-78fd-da40-3f58-5e6e78c469f0.png)
![スクリーンショット 2019-12-06 13.25.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/ed270844-5bb5-cf0d-fbcf-533730f161b3.png)

アプリを作成したらClientID,SecretIDを確認しましょう。
次に、ページ右にあるEDIT SETTINGからRedirect URIsを登録しよう。
![スクリーンショット 2019-12-06 13.29.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/3924a802-b631-e3c1-40a7-c37e80a0cc3a.png)
![スクリーンショット 2019-12-06 13.34.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/008ab0e2-5727-e5f3-6828-ecce55c10782.png)

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

```ruby:Gemfile
gem 'devise'
```
`$ bundle install`
`$ rails g devise:install`

`$ rails g devise User`
`$ rails db:migrate`
![スクリーンショット 2019-12-05 12.30.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470271/9607bdb5-f77c-4f09-a168-cf4aedd32ccf.png)

```erb: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>
```

```ruby: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キーを管理しましょう。

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

`$ bundle install`

`$ rails g controller users`

```ruby: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ファイルを作成し、その中に入れましょう。

```ruby:.env
SPOTIFY_CLIENT_ID=your client ID
SPOTIFY_SECRET_ID=your secret ID
SPOTIFY_REDIRECT_URI=http://localhost:3000/tests/
```
```erb: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`

```ruby:tests_controller.rb
class TestsController < ApplicationController
    def index
    end
end
```

```erb:index.html.erb
ログインできました。
お疲れ様でした！
```

```ruby: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時間です。１時間でaccess_tokenが切れるのでユーザーは１時間ごとにログインをし直しましょう♬
http://localhost:3000/tests/#access_token=xxx&token_type=Bearer&expires_in=3600&state=caGLGQ8xzz1yYbH7

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