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?