Trouble refreshing Spotify token iOS SDK

2019-05-31 04:54发布

I'm confused as how I use the refresh token service. In my app there is a section with many playlists. When the user clicks on the playlist it runs this code:

 func checkAuth() {
    print("checking auth")
    let auth = SPTAuth.defaultInstance()
    //print(auth!.session.isValid())
    if auth!.session == nil {
        print("no auth")
        if auth!.hasTokenRefreshService {
            print("refresh token if session == nil")
            self.renewTokenAndShowPlayer()
            return
        } else {
            self.performSegue(withIdentifier: "LoginControllerSegue", sender: nil)
        }
        return
    }
    if auth!.session.isValid() && firstLoad {
        // It's still valid, show the player.
        print("valid auth")
        self.showPlayer()
        return
    }

    if auth!.hasTokenRefreshService {
        print("refresh token")
        self.renewTokenAndShowPlayer()
        return
    }
}


    func renewTokenAndShowPlayer() {
    SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
        SPTAuth.defaultInstance().session = session
        if error != nil {
            print("Refreshing token failed.")
            print("*** Error renewing session: \(error)")
            self.performSegue(withIdentifier: "LoginControllerSegue", sender: nil)
            return
        }
        self.showPlayer()
    }
}

So let's say the user hasn't logged in yet and they goto the login player, then get authenticated.

Later, when they close the player and click on a different playlist, they are brought to the login screen again. Why is this?

I believe my refresh token service works, because whenever it is called after someone logs in, my server get's a /swap 200. Also, this only calls whenever someone comes back to the app (to the LoginViewController) after logging into Spotify, why is this?

Here is the code for my login page:

import UIKit
import WebKit

class LoginViewController: UIViewController, SPTStoreControllerDelegate, WebViewControllerDelegate {

    @IBOutlet weak var statusLabel: UILabel!
    var authViewController: UIViewController?
    var firstLoad: Bool!
    var Information: [String:String]?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.statusLabel.text = ""
        self.firstLoad = true

        let auth = SPTAuth.defaultInstance()


        NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)


        // Check if we have a token at all
        if auth!.session == nil {
            self.statusLabel.text = ""
            return
        }
        // Check if it's still valid
        if auth!.session.isValid() && self.firstLoad {
            // It's still valid, show the player.
            print("View did load, still valid, showing player")
            self.showPlayer()
            return
        }
        // Oh noes, the token has expired, if we have a token refresh service set up, we'll call tat one.
        self.statusLabel.text = "Token expired."
        print("Does auth have refresh service? \(auth!.hasTokenRefreshService)")
        if auth!.hasTokenRefreshService {
            print("trying to renew")
            self.renewTokenAndShowPlayer()
            return
        }

        // Else, just show login dialog
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

    }

    override var prefersStatusBarHidden: Bool {
        return true
    }

    func getAuthViewController(withURL url: URL) -> UIViewController {
        let webView = WebViewController(url: url)
        webView.delegate = self

        return UINavigationController(rootViewController: webView)
    }

    func sessionUpdatedNotification(_ notification: Notification) {
        self.statusLabel.text = ""
        let auth = SPTAuth.defaultInstance()
        self.presentedViewController?.dismiss(animated: true, completion: { _ in })
        if auth!.session != nil && auth!.session.isValid() {
            self.statusLabel.text = ""
            print("Session updated, showing player")
            self.showPlayer()
        }
        else {
            self.statusLabel.text = "Login failed."
            print("*** Failed to log in")
        }
    }

    func showPlayer() {
        self.firstLoad = false
        self.statusLabel.text = "Logged in."
        self.Information?["SpotifyUsername"] = SPTAuth.defaultInstance().session.canonicalUsername

        OperationQueue.main.addOperation {
            [weak self] in
            self?.performSegue(withIdentifier: "ShowPlayer", sender: self)
        }
        //self.performSegue(withIdentifier: "ShowPlayer", sender: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowPlayer" {
            if let destination = segue.destination as? PlayController {
                destination.Information = self.Information
            }
        }
    }

    internal func productViewControllerDidFinish(_ viewController: SPTStoreViewController) {
        self.statusLabel.text = "App Store Dismissed."
        viewController.dismiss(animated: true, completion: { _ in })
    }

    func openLoginPage() {
        self.statusLabel.text = "Logging in..."
        let auth = SPTAuth.defaultInstance()
        if SPTAuth.supportsApplicationAuthentication() {
            self.open(url: auth!.spotifyAppAuthenticationURL())
        } else {
            // storyboard?.instantiateViewController(withIdentifier: <#T##String#>)
            //
            self.authViewController = self.getAuthViewController(withURL: SPTAuth.defaultInstance().spotifyWebAuthenticationURL())
            self.definesPresentationContext = true
            self.present(self.authViewController!, animated: true, completion: { _ in })
        }
    }

    func open(url: URL) {
        if #available(iOS 10, *) {
            UIApplication.shared.open(url, options: [:],
                                      completionHandler: {
                                        (success) in
                                        print("Open \(url): \(success)")
            })
        } else {
            let success = UIApplication.shared.openURL(url)
            print("Open \(url): \(success)")
        }
    }

    func renewTokenAndShowPlayer() {
        self.statusLabel.text = "Refreshing token..."
        print("trying to renew")
        SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
            SPTAuth.defaultInstance().session = session
            if error != nil {
                self.statusLabel.text = "Refreshing token failed."
                print("*** Error renewing session: \(error)")
                return
            }
            print("refreshed token")
            self.presentedViewController?.dismiss(animated: true, completion: { _ in })
            self.showPlayer()
        }
    }

    func webViewControllerDidFinish(_ controller: WebViewController) {
        // User tapped the close button. Treat as auth error
        print("UI Web view did finish")
        let auth = SPTAuth.defaultInstance()
        // Uncomment to turn off native/SSO/flip-flop login flow
        //auth.allowNativeLogin = NO;
        // Check if we have a token at all
        if auth!.session == nil {
            self.statusLabel.text = ""
            return
        }
        // Check if it's still valid
        if auth!.session.isValid() && self.firstLoad {
            // It's still valid, show the player.
            print("Still valid, showing player")
            self.showPlayer()
            return
        }
    }

    @IBAction func loginButtonWasPressed(_ sender: SPTConnectButton) {
        self.openLoginPage()
    }

    @IBAction func showSpotifyAppStoreClicked(_ sender: UIButton) {
        self.statusLabel.text = "Presenting App Store..."
        let storeVC = SPTStoreViewController(campaignToken: "your_campaign_token", store: self)
        self.present(storeVC!, animated: true, completion: { _ in })
    }

    @IBAction func clearCookiesClicked(_ sender: UIButton) {
        let storage = HTTPCookieStorage.shared
        for cookie: HTTPCookie in storage.cookies! {
            if (cookie.domain as NSString).range(of: "spotify.").length > 0 || (cookie.domain as NSString).range(of: "facebook.").length > 0 {
                storage.deleteCookie(cookie)
            }
        }
        UserDefaults.standard.synchronize()
        self.statusLabel.text! = "Cookies cleared."
    }

    @IBAction func dismissViewController () {
        self.dismiss(animated: true, completion: {})
    }
}

And here is my node.js code:

var spotifyEndpoint = 'https://accounts.spotify.com/api/token';

/**
 * Swap endpoint
 *
 * Uses an authentication code on req.body to request access and
 * refresh tokens. Refresh token is encrypted for safe storage.
 */
app.post('/swap', function (req, res, next) {
    var formData = {
            grant_type : 'authorization_code',
            redirect_uri : clientCallback,
            code : req.body.code
        },
        options = {
            uri : url.parse(spotifyEndpoint),
            headers : {
                'Authorization' : authorizationHeader
            },
            form : formData,
            method : 'POST',
            json : true
        };

    console.log("Options" + options);
    request(options, function (error, response, body) {
        if (response.statusCode === 200) {
            body.refresh_token = encrpytion.encrypt(body.refresh_token);
        } else {
          console.log("error swapping: " + error);
        }

        res.status(response.statusCode);
        res.json(body);
    });
});

app.post('/refresh', function (req, res, next) {
    if (!req.body.refresh_token) {
        res.status(400).json({ error : 'Refresh token is missing from body' });
        return;
    }

    var refreshToken = encrpytion.decrypt(req.body.refresh_token),
        formData = {
            grant_type : 'refresh_token',
            refresh_token : refreshToken
        },
        options = {
            uri : url.parse(spotifyEndpoint),
            headers : {
                'Authorization' : authorizationHeader
            },
            form : formData,
            method : 'POST',
            json : true
        };

    request(options, function (error, response, body) {
        if (response.statusCode === 200 && !!body.refresh_token) {
            body.refresh_token = encrpytion.encrypt(body.refresh_token);
        }

        res.status(response.statusCode);
        res.json(body);
    });
});

The LoginViewController is only skipped after I log in once, are shown the player, quit the app, then start the app again and click on a song. Why is this?

(Note, this is a error I get from refreshing token: *** Error renewing session: Optional(Error Domain=com.spotify.auth Code=400 "No refresh token available in the session!" UserInfo={NSLocalizedDescription=No refresh token available in the session!}))

I got my refresh code from [this] project. Is Heroku necessary?

0条回答
登录 后发表回答