Search code examples
swiftyoutube-apiyoutube-data-api

How to fix 401 errors with YouTube API queries?


[The problem is solved, I actually just can't select my answer as the solution yet.]

I'm working on a video upload feature for a mobile app, with YouTube Data API; but I keep having a 401 error at the query's execution.

I'm using Firebase to provide me OAuth & API keys from Google Developer.


I tried to see if the PLIST file furnished by Firebase suffered any change, and even with an other.

I tried to reset the iOS Simulator, to uninstall the app and to terminate the permissions from my account for the app through GIDSignIn.sharedInstance().disconnect().

I tried to see with other StackOverFlow questions, but they all are about other languages, or even "raw" HTTP requests.


In my AppDelegate file, I have those lines for the connection with Firebase and Google:

FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID

and this method (and a very similar one that I won't show here):

func application( _ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any ) -> Bool {
        return GIDSignIn.sharedInstance().handle( url,
                sourceApplication: sourceApplication,
                annotation: annotation )
}

In my controller associated class, I have those methods to create the query:

private func uploadVideo( withVideoObject video: GTLRYouTube_Video, resumeUploadLocationURL locationURL: URL? ) throws {
    let query = try uploadQuery( _video: video, resumeUploadLocationURL: locationURL )!

    // [ progress related feedback ]

    YouTubeUploadController.uploadFileTicket = self.service.executeQuery( query,
            delegate: self.controller!,
            didFinish: #selector( self.controller!.displayResultWithTicket(ticket:finishedWithObject:error:) ) )
}
private func uploadQuery( _video: GTLRYouTube_Video, resumeUploadLocationURL locationURL: URL? ) throws -> GTLRYouTubeQuery_VideosInsert? {
    let fileToUploadURL = URL( fileURLWithPath: "\(self.controller!.uploadPathField)" )
    if !( try! fileToUploadURL.checkPromisedItemIsReachable() ) {
        throw YouTubeCustomError.init( _kind: .fileNotFound )
    }
    // Get a file handle for the upload data.
    let uploadParameters = prepareUploadParameters( fileToUploadURL: fileToUploadURL, locationURL: locationURL )

    return GTLRYouTubeQuery_VideosInsert.query( withObject: _video, part: "snippet,status", uploadParameters: uploadParameters )
}

It sends an error through displayResultWithTicket(ticket:finishedWithObject:error:):
"This operation couldn't be completed. (com.google.HTTPStatus error 401.)"

No given failureReason, nor recoverySuggestion.

Thanks in advance.


Response body:
Here's the GTMSessionFetcher's HTTP answer logs for an upload attempt:

Response body: (178 bytes)
{
  "error" : {
    "message" : "Login Required",
    "errors" : [
      {
        "message" : "Login Required",
        "locationType" : "header",
        "reason" : "required",
        "domain" : "global",
        "location" : "Authorization"
      }
    ],
    "code" : 401
  }
}

Request Headers

Request: POST https://www.googleapis.com/resumable/upload/youtube/v3/videos?part=snippet%2Cstatus&prettyPrint=false
Request headers:
  Accept: application/json
  Cache-Control: no-cache
  Content-Type: application/json; charset=utf-8
  User-Agent: org.cocoapods.GoogleAPIClientForREST/1.3.8 google-api-objc-client/3.0 iPhone/12.2 hw/sim (gzip) (GTMSUF/1)
  X-Goog-Upload-Command: start
  X-Goog-Upload-Content-Length: 2614231
  X-Goog-Upload-Content-Type: video/mp4
  X-Goog-Upload-Protocol: resumable

Solution

  • So in my case, I was lacking the GTRLService authorizer affectation in my GIDSignInDelegate signIn method:
    service.authorizer = user.authentication.fetcherAuthorizer()

    I assume it can be also done in other methods, but it is the core point in which it is initialized.


    I did it through an ObjC write accessor, because I didn't find a way to create/obtain a GTRLService instance in Swift and I used a ObjC singleton class to obtain it, and I was unsure if we could access the GTRLService instance methods in Swift if it was initialized in ObjC.

    + (void)setSharedInstanceAuthorizer:(id <GTMFetcherAuthorizationProtocol>)_authorizer {
        if (sharedInstance == nil) {
            sharedInstance = [GTLRYouTubeService new];
        }
        sharedInstance.authorizer = _authorizer;
    };
    

    If that doesn't solve the problem, then @stvar's answers in the comments of the question might actually do.