0

I'm trying to follow Last.fm's tutorial to consume their API in an iOS app (using Swift), but I don't know what's wrong with my code. They ask to do something called Last.fm method signature which I'm not understanding how to insert into my program. Here is the link to the tutorial I try to follow: https://www.last.fm/api/mobileauth

Here is my current code:

import UIKit

struct LoginRequestBody: Codable { 
    var username: String 
    var password: String 
    var api_key: String 
    var api_sig: String 
}

enum AuthenticationError: Error { 
    case invalidCredentials 
    case custom(errorMessage: String) 
}

class APIService {
    func requestAPI(username: String, password: String, api_key: String, api_sig: String) {
        guard let url = URL(string: "http://www.last.fm/api/auth/?api_key=xxx") else {
            return
        }
        
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        
        let body = LoginRequestBody(username: username, password: password, api_key: api_key, api_sig: api_sig)
        let bodyStr = [
            "username": "\(body.username)",
            "password": "\(body.password)",
            "api_key": "\(body.api_key)",
            "api_sig": "\(body.api_sig)"]
        
        urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: bodyStr, options: .fragmentsAllowed)
        
        let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
            guard let data = data, error == nil else {
                return
            }
            
            do {
                let response = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
                print(response)
            } catch {
                print(error)
            }
        }
        task.resume()
    }
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
  • You must know something, does it compile, do you get a runtime error, do you get a response from your api call, is anything printed in the console? – Joakim Danielson May 21 '23 at 15:32
  • APIRequest[*] Task <*>.<1> finished with error [-1022] Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLStringKey=http://www.last.fm/api/auth/?api_key=xxx, NSErrorFailingURLKey=http://www.last.fm/api/auth/?api_key=xxx, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <*>.<1>"), – Mel P. User May 21 '23 at 16:01
  • continue- _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <577985DE-A164-43D1-BEF0-30C0CA85C1EC>.<1>, NSUnderlyingError=0x6000004d12c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}} – Mel P. User May 21 '23 at 16:02
  • 1
    Use https instead in your url. – Joakim Danielson May 21 '23 at 16:09

2 Answers2

1

You are seeing the error you mentioned in your comments because you try to connect to an endpoint using http instead of https and Apples App Transport Security mechanism blocks you from doing that.

You have two options (maybe more :))

  1. See if Last.fm supports https instead and if they to, then use that
  2. Allow your app to use http instead. You do so by adding this to your info.plist
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Before you do so though, please see also this great answer describing all the risks you take if you use http over https

pbodsk
  • 6,787
  • 3
  • 21
  • 51
0

You should definitely use the secure https://ws.audioscrobbler.com/2.0/auth.getMobileSession endpoint like it is mentioned in the documentation. That is also important because you send sensitive data over the wire like the user's password.

Next you should generate the body of your POST request.

If you struggle to generate the signature, here's how:

  1. Order all parameters (i.e. key-value pairs) of your request and the endpoint method alphabetically, e.g.
    • api_key -> your_key
    • method -> auth.getMobileSession
    • password -> somebodys_password
    • username -> somebody
  2. Concatenate them to one string like the schema key1value1key2...keyNvalueN, e.g.
    • api_keyyour_keymethodauth.getMobileSessionpassword...usernamesomebody
  3. Append your API secret (let's assume it has the value apisecret) to that string (and only that value apisecret)
    • api_keyyour_keymethodauth.getMobileSessionpassword...usernamesomebodyapisecret
    • You've got that API secret when you registered your App at last.fm, along with your API key
  4. Generate the MD5 hash of that string
    • not sure how this would be done in Swift but Google and Stackoverflow are your friends
    • be sure your have correctly UTF-8 encoded all the strings
  5. This hash is the signature you will send as api_sig in your request

This is also the way to generate the signature for other API calls.

Note: when you use the getMobileSession approach you won't redirect the user to a last.fm page where they could allow your app to access their profile. Instead you are sending directly the user's credentials to last.fm to authenticate.

Note: for me the body of such a HTTP POST request was assembled like key1=value1&key2=value2&... maybe Swift is doing this internally already.

jmizv
  • 1,172
  • 2
  • 11
  • 28