Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly handle token refresh with Spotify SDK and Swift 3. Error Code=3840

Tags:

ios

swift

spotify

tl;dr I'm receiving: JSON text did not start with array or object and option to allow fragments not set. if i'm trying to receive a token and No refresh token available in the session! if I'm trying to renew a token.

I'm trying to setup the token refresh for the Objective-C Spotify iOS SDK beta-25 in Swift 3. I'm using a Heroku Server and the Ruby script provided by Spotify, changed to my credentials.

require 'sinatra'
require 'net/http'
require 'net/https'
require 'base64'
require 'encrypted_strings'
require 'json'

CLIENT_ID = ENV['xxx']
CLIENT_SECRET = ENV['xxx']
ENCRYPTION_SECRET = ENV['xxx']
CLIENT_CALLBACK_URL = ENV['xxx://returnafterlogin']
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")

get '/' do
"Working"    
end

post '/swap' do

    # This call takes a single POST parameter, "code", which
    # it combines with your client ID, secret and callback
    # URL to get an OAuth token from the Spotify Auth Service,
    # which it will pass back to the caller in a JSON payload.

    auth_code = params[:code]

    http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new("/api/token")

    request.add_field("Authorization", AUTH_HEADER)

    request.form_data = {
        "grant_type" => "authorization_code",
        "redirect_uri" => CLIENT_CALLBACK_URL,
        "code" => auth_code
    }

    response = http.request(request)

    # encrypt the refresh token before forwarding to the client
    if response.code.to_i == 200
        token_data = JSON.parse(response.body)
        refresh_token = token_data["refresh_token"]
        encrypted_token = refresh_token.encrypt(:symmetric, :password => ENCRYPTION_SECRET)
        token_data["refresh_token"] = encrypted_token
        response.body = JSON.dump(token_data)
    end

    status response.code.to_i
    return response.body
end

post '/refresh' do

    # Request a new access token using the POST:ed refresh token

    http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new("/api/token")

    request.add_field("Authorization", AUTH_HEADER)

    encrypted_token = params[:refresh_token]
    refresh_token = encrypted_token.decrypt(:symmetric, :password => ENCRYPTION_SECRET)

    request.form_data = {
        "grant_type" => "refresh_token",
        "refresh_token" => refresh_token
    }

    response = http.request(request)

    status response.code.to_i
    return response.body

end

Set by:

SPTAuth.defaultInstance().tokenSwapURL = URL(string: SpotifyCredentials.tokenSwapURLSwap)
SPTAuth.defaultInstance().tokenRefreshURL = URL(string: SpotifyCredentials.tokenSwapURLRefresh)

Now the user is not able to login anymore and I'm receiving the error posted on top. If I'm deleting tokenSwapURL and tokenRefreshURL, everything works again, but the User has to re-auth every 60 minutes.

If I'm trying to refresh the Token with an already logged in user, I receive:

"No refresh token available in the session!"

if SPTAuth.defaultInstance().session != nil {
        print("needs login")
        SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session, callback: { error, session in
            if error != nil {
                print("\(error?.localizedDescription)") // "No refresh token available in the session!"
                return
            }
        })
}

What am I missing? Help is very appreciated.

like image 755
David Seek Avatar asked Nov 26 '16 00:11

David Seek


People also ask

How to create a Spotify refresh token?

The solution is to manually generate a Spotify refresh token then use that to create an access token when needed. Visit your Spotify developers dashboard then select or create your app. Note down your Client ID, Client Secret, and Redirect URI in a convenient location to use in Step 2.

How can I get a refresh token from the iOS-SDK?

By setting tokenSwapURL and tokenRefreshURL it is possible for the iOS-SDK to request a new access token with a refresh token whenever needed. The iOS-SDK demo project has a ruby example of the needed back-end services.

Does Spotify support OAuth Code grant?

If a longer session is desired Spotify account service supports the OAuth Code grant flow. The iOS-SDK provides helper functionality to simplify the use of the Code grant flow. By setting tokenSwapURL and tokenRefreshURL it is possible for the iOS-SDK to request a new access token with a refresh token whenever needed.

How to avoid Spotify login pop-up during authorization flow?

Since the job runs in the background I needed a way to avoid the Spotify login pop-up during the authorization flow. The solution is to manually generate a Spotify refresh token then use that to create an access token when needed.


2 Answers

I have been able to create a token refresh service for Spotify with the following Git:

https://github.com/adamontherun/SpotifyTokenRefresh

All you need to do is to follow the instructions of the Heroku link within the git project.

enter image description here

I have tried to get in contact with the author of the project, but he wasn't able to tell me, why my approach wasn't working but his is. All I can leave you with is this working Deploy to Heroku link.

like image 148
David Seek Avatar answered Oct 10 '22 02:10

David Seek


Short summary: assuming your JSON parsing works properly, the problem is malformed JSON (server-side).


JSON text did not start with array or object and option to allow fragments not set

can be thrown by 's JSONSerialization.jsonObject(with:options:) which returns

A Foundation object from the JSON data in data, or

nil if an error occurs.

The malformed JSON » nil instead of token » current token gets nilled » result: "the user is not able to login anymore"

Possible explanations for getting malformed JSON include:

It usually is because of some warning message throwing out from your server without putting it in the response array. For example in PHP, some "warning messages" are not caught in your array so that when you finally use "echo json_encode($RESPONSE_ARR)," it is not a JSON format. — https://stackoverflow.com/a/38680699/3419541

From the same SO page:

You need to debug this in the iOS application. First convert the data to a string and print that and check it. If the string looks alright then print the data itself - sometimes people manage to add 0 bytes or control characters, or two byte order markers or something similar which are invisible in the string but are not legal JSON. — https://stackoverflow.com/a/38681179/3419541

like image 33
PDK Avatar answered Oct 10 '22 02:10

PDK