从 javascript [spotify auth] 中的弹出窗口获取访问令牌 url

Posted

技术标签:

【中文标题】从 javascript [spotify auth] 中的弹出窗口获取访问令牌 url【英文标题】:getting access token url from popup in javascript [spotify auth] 【发布时间】:2020-04-02 01:35:20 【问题描述】:

我正在尝试在纯 javascript 中创建一个 spotify 身份验证流程,以便用户可以登录,然后我可以为他们的帐户添加一个新的播放列表。根据我阅读的说明,我使用了一个身份验证弹出窗口,一旦他们登录,就会在 URL 中包含访问令牌。我现在有一个弹出窗口,用户可以使用它进行身份验证,一旦他们这样做,它将在 url 中有访问令牌。

我需要从弹出窗口中获取 url 并将其保存为全局变量,但我无法弄清楚如何在 javascript 中执行此操作。

https://codepen.io/martin-barker/pen/YzPwXaz

我的 codepen 打开一个带有 let popup = window.open( 的弹出窗口,我可以在我的弹出窗口中运行一个函数来检测用户何时成功通过身份验证并且 url 发生变化?在这种情况下,我想保存用于解析的 url 并关闭我的弹出窗口

我的javascript代码如下:

async function spotifyAuth() 
let result = spotifyLogin()


//open popup
function spotifyLogin() 
console.log("inside spotifyLogin, opening popup")

let popup = window.open(`https://accounts.spotify.com/authorize?client_id=5a576333cfb1417fbffbfa3931b00478&response_type=token&redirect_uri=https://codepen.io/martin-barker/pen/YzPwXaz&show_dialog=true&scope=playlist-modify-public`, 'Login with Spotify', 'width=800,height=600')



//get url from popup and parse access token????
window.spotifyCallback = (payload) => 
console.log("inside window? ") //this line never appears in console
popup.close()
fetch('https://api.spotify.com/v1/me', 
headers: 
'Authorization': `Bearer $payload`

).then(response => 
return response.json()
).then(data => 
// do something with data
)

【问题讨论】:

【参考方案1】:

这是我在 JavaScript 中的做法。像你提到的全局变量:

var access_token = null;

我的网址看起来像这样,例如:https://...home.jsp#access_token=BQAXe5JQOV_xZmAukmw6G430lreF......rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s&token_type=Bearer&expires_in=3600&state=vURQeVAoZqwYm4dC

在 Spotify 将用户重定向到您在仪表板上指定的 uri 后,我会解析包含访问令牌的哈希的 url,如下所示:

var hash = window.location.hash.substring(1);
var accessString = hash.indexOf("&");

/* 13 because that bypasses 'access_token' string */
access_token = hash.substring(13, accessString);
console.log("Access Token: " + access_token);

输出是什么:

Access Token: BQAXe5JQOV_xZmAukmw6G430lreF...........rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s

我将这个访问令牌保存在 sessionStorage 中,以防万一用户离开页面并且 url 不包含 access_token。我假设这是隐式授权流程,因为您想使用纯 JavaScript。只需确保在过期后每隔一小时重新获取一次访问令牌即可。

附录

我可以通过示例向您展示如何获取令牌并使用它。

我在一个 .html 页面上有一个按钮,一旦单击该按钮,它就会在名为

的 JavaScript 文件中调用一个名为 implicitGrantFlow() 的函数

Test.js

function implicitGrantFlow() 

/* If access token has been assigned in the past and is not expired, no request required. */
if (sessionStorage.getItem("accessToken") !== null &&
    sessionStorage.getItem("tokenTimeStamp") !== null &&
    upTokenTime < tokenExpireSec) 
        var timeLeft = (tokenExpireSec - upTokenTime);
        console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");

        /* Navigate to the home page. */
        $(location).attr('href', "home.jsp");
 else 
    console.log("Token expired or never found, getting new token.");
    $.ajax(
        url: auth_url,
        type: 'GET',
        contentType: 'application/json',
        data: 
            client_id: client_id,
            redirect_uri: redirect_uri,
            scope: scopes,
            response_type: response_type_token,
            state: state
        
    ).done(function callback(response) 
        /* Redirect user to home page */
        console.log("COULD THIS BE A SUCCESS?");
        $(location).attr('href', this.url);

    ).fail(function (error) 
        /* Since we cannot modify the server, we will always fail. */
        console.log("ERROR HAPPENED: " + error.status);
        console.log(this.url);
        $(location).attr('href', this.url);
    );

我正在做的是检查我存储在 sessionStorage 中的 access_token 信息是否为空。我使用 Epoch 以来的时间来生成令牌的创建时间以及理想情况下应该到期的时间。如果满足这些参数,那么我不会再拨打电话。

否则,我拨打电话以获取访问令牌,成功后会将我重定向到我的 uri,正如我在之前的文章中提到的那样(你会看到我在 .fail 部分中有重定向。这是由于对我来说,我没有权限在我的学校服务器上设置设置以绕过 CORS 相关问题,即使我创建的重定向 url 很好,也会阻止我的呼叫成功。)。

然后,当我的白名单 uri 被加载(重定向到我的主页)时,我会使用我的 &lt;body&gt; 标签。

home.jsp

<body onload="getAccessToken()">

在我的标签中,一旦页面加载,我就会调用此函数。这会调用函数 getAccessTokens()。

/**
 * The bread and butter to calling the API. This function will be called once the
 * user is redirected to the home page on success and without rejecting the terms
 * we are demanding. Once through, this function parses the url for the access token
 * and then stores it to be used later or when navigating away from the home page.
 */
function getAccessToken() 

    access_token = sessionStorage.getItem("accessToken");

    if (access_token === null) 
        if (window.location.hash) 
            console.log('Getting Access Token');

            var hash = window.location.hash.substring(1);
            var accessString = hash.indexOf("&");

            /* 13 because that bypasses 'access_token' string */
            access_token = hash.substring(13, accessString);
            console.log("Access Token: " + access_token);

            /* If first visit or regaining token, store it in session. */    
            if (typeof(Storage) !== "undefined") 
                /* Store the access token */
                sessionStorage.setItem("accessToken", access_token); // store token.

                /* To see if we need a new token later. */
                sessionStorage.setItem("tokenTimeStamp", secondsSinceEpoch);

                /* Token expire time */
                sessionStorage.setItem("tokenExpireStamp", secondsSinceEpoch + 3600);
                console.log("Access Token Time Stamp: "
                + sessionStorage.getItem("tokenTimeStamp")
                + " seconds\nOR: " + dateNowMS + "\nToken expires at: "
                + sessionStorage.getItem("tokenExpireStamp"));
             else 
                alert("Your browser does not support web storage...\nPlease try another browser.");
            
         else 
            console.log('URL has no hash; no access token');
        
     else if (upTokenTime >= tokenExpireSec) 
        console.log("Getting a new acess token...Redirecting");

        /* Remove session vars so we dont have to check in implicitGrantFlow */
        sessionStorage.clear();

        $(location).attr('href', 'index.html'); // Get another access token, redirect back.

     else 
        var timeLeft = (tokenExpireSec - upTokenTime);
        console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");
    

一旦我从 url 获得访问令牌,我就会将令牌存储在会话存储中。我使用了我之前文章中提到的过程,但这里是完整的 JavaScript。如果在 cmets 之后仍然不清楚,请告诉我。

现在我们已经获得并存储了访问令牌,我们现在可以进行 api 调用。以下是我的做法(并且一直在使用 qQuery,这是获取用户热门曲目的示例)。

示例 api 调用

/**
 * Function will get the user's top tracks depending on the limit and offset
 * specified in addition to the time_range specified in JSON format.
 * @param time_range short/medium/long range the specifies how long ago.
 * @param offset Where the indexing of top tracks starts.
 * @param limit How many tracks at a time we can fetch (50 max.)
 */
function getUserTopTracks(time_range, offset, limit) 

$.get(
    url: 'https://api.spotify.com/v1/me/top/tracks',
    headers: 
        'Authorization': 'Bearer ' + access_token,
    ,
    data: 
        limit: limit, // This is how many tracks to show (50 max @ a time).
        offset: offset, // 0 = top of list, increase to get more tracks.
        time_range: time_range // short/medium/long_term time ranges.
    ,
    success: function (response) 

        /* Get the items from the response (The limit) tracks. */
        res = JSON.parse(JSON.stringify(response.items));

        /* Get all the track details in the json */
        for (i = 0; i < res.length; i++) 
            console.log("Track: " + res[i]);
        
    ,
    fail: function () 
        console.log("getUserTopTracks(): api call failed!");
    
);

参数 time_range 被指定为“long_term”,以获取用户自开始以来的热门曲目(阅读更多关于 Spotify 的文档以获取更多信息)除了偏移量为 0 以从开头开始并且限制等于 50 以来是每次调用的最大获取次数。

成功时,我有我的响应变量“响应”,然后我希望解析的根从“项目”部分开始,以使解析更容易(您不必这样做,您只需使用 response.xxx .items.xxx)。然后我将响应打印到控制台。

这是您可以做的基本事情,您决定如何处理或存储数据取决于您。我不是专家,我上个学期才开始学习网络编程,我正在做的很多实践可能是错误的或不正确的。

【讨论】:

你有你使用的js代码的例子吗?我不明白的部分是我如何获得令牌。我的弹出窗口打开,用户登录,然后我将它重定向回 codepen,弹出窗口现在不会自动关闭,但是一旦用户登录,我就想关闭弹出窗口。我应该向弹出窗口发送它自己的 javascript 代码来解析 url 吗?或者有什么方法可以在用户登录时添加一个事件侦听器函数,这样一旦访问带有令牌的重定向 url,我就会运行字符串解析内容以获取访问令牌 感谢您的所有建议,我一直在研究它,并且正在努力通过初始 ajax 请求到身份验证端点,但我正在尝试修复限制我的 url 重定向的 CORS 错误:@987654322 @ @Martin 我知道发生了什么。请参阅此链接:***.com/questions/20035101/… 以获得正确的解释。基本上,在我的本地机器上测试之前,我所做的就是在 Firefox 上安装“CORS Everywhere”插件。如果您不使用 Firefox,那么我相信 Chrome 也有类似的插件。启用后,它将绕过 CORS 策略并允许您检查您的请求是否真正有效。试试这个,看看它是否有效。 我将 upTokenTime 声明为全局变量,使用: const tokenSecOld = sessionStorage.getItem("tokenTimeStamp");常量 tokenExpireSec = sessionStorage.getItem("tokenExpireStamp"); const upTokenTime = parseInt(tokenSecOld) + (secondsSinceEpoch - parseInt(tokenSecOld));我基本上只是利用 Epoch 以来的时间来限制令牌有效的时间,并确保令牌还有时间可以使用。 感谢您的帮助,我已成功生成/保存在我的页面上的访问令牌。我正在尝试通过调用您的函数“getUserTopTracks('long_term', 0, 15)' 来调用 api 以获取热门曲目。我检查了我的访问令牌字符串是否在 get 请求中,但我收到 403 控制台错误:GET api.spotify.com/v1/me/top/… 403【参考方案2】:

你是在正确的轨道上。

弹出窗口会将您重定向到您在redirect_uri=... 下添加的网站。它会将代码和状态查询参数添加到该 url。

所以在作为redirect_uri主机的网页上,你可以解析完整的URL。

不可能在单个页面上完成。

【讨论】:

以上是关于从 javascript [spotify auth] 中的弹出窗口获取访问令牌 url的主要内容,如果未能解决你的问题,请参考以下文章

从 Spotify Web 服务返回前五个值

从 Spotify API 获取访问令牌时出错 [重复]

在 Javascript 中调试 Spotify 应用程序——必须有更好的方法 [关闭]

使用 spotify-web-api-js 在 javascript 中获取 Spotify 艺术家的热门曲目

在 Spotify(Javascript)中进行 ajax 调用后没有保存 Cookie?

通过 spotify web api 和 javascript 播放歌曲的问题