Youtube v3 api 401 Unauthorized

2019-07-18 07:34发布

问题:

I am trying to upload a file using v3 of the youtube api and without using the c# library. I really can't use the library because I am making my own library that allows me to use a few common apis (youtube, vimeo, facebook, etc)

I have already got my access token and refresh token which is fine. Now I need to upload a file using the youtube resumable uploads defined here:

https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol

but for some reason my code is coming back with a 401 Unauthorized error but I can't see why. Here is my code that is creating the request:

    private void CreateUploadRequest(SynchronisedAsset asset)
    {
        var endPoint = api.ApiUrl + "/videos?uploadType=resumable&part=snippet&key=" + api.Tokens.ConsumerKey; // read for the different ways to interact with videos https://developers.google.com/youtube/v3/docs/#Videos
        var maxSize = 68719476736; // 64 gig

        try
        {
            var location = CompanyProvider.GetUploadLocation(this.baseUploadDirectory, companyId, FileType.Asset);
            var filePath = System.IO.Path.Combine(location, asset.FileName);
            using (var data = new FileStream(filePath, FileMode.Open))
            {
                if (maxSize > data.Length && (asset.MimeType.ToLower().StartsWith("video/") || asset.MimeType.ToLower().Equals("application/octet-stream")))
                {
                    var json = "{ \"snippet\": { \"title\": \"" + asset.FileName + "\", \"description\": \"This is a description of my video\" } }";
                    var buffer = Encoding.ASCII.GetBytes(json);

                    var request = WebRequest.Create(endPoint);
                    request.Headers[HttpRequestHeader.Authorization] = string.Format("Bearer {0}", api.Tokens.AccessToken);
                    request.Headers["X-Upload-Content-Length"] = data.Length.ToString();
                    request.Headers["X-Upload-Content-Type"] = asset.MimeType;
                    request.ContentType = "application/json; charset=UTF-8";
                    request.ContentLength = buffer.Length;
                    request.Method = "POST";

                    using (var stream = request.GetRequestStream())
                    {
                        stream.Write(buffer, 0, (int)buffer.Length);
                    }

                    var response = request.GetResponse();
                }
            }
        }
        catch (Exception ex)
        {
            eventLog.WriteEntry("Error uploading to youtube.\nEndpoint: " + endPoint + "\n" + ex.ToString(), EventLogEntryType.Error);
        }
    }

api.ApiUrl is https://www.googleapis.com/youtube/v3. I am not sure if the key is needed, it doesn't show it in the documentation but I added it to see if I could solve my Unauthorized issue. Also, I figured that without the key, how would it know what account to upload to?

Can anyone see what is wrong with my code?

Any help would be greatly appreciated.

Update 1

After a bit of time sorting stuff out, I have now added some code that checks the youtube credentials before trying to do an upload. This is done with these bits of code:

    public string GetAuthorizeApplicationUrl()
    {
        var sb = new StringBuilder();
        var dictionary = new Dictionary<string, string>
        {
            {"client_id", Tokens.ConsumerKey},
            {"redirect_uri", callbackUrl},
            {"response_type", "code"},
            {"scope", "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtubepartner"},
            {"approval_prompt", "force"},
            {"access_type", "offline"},
            {"state", ""}
        };

        sb.Append(requestTokenUrl);
        foreach (var parameter in dictionary)
        {
            var query = (sb.ToString().Contains("?")) ? "&" : "?";
            sb.Append(query + parameter.Key + "=" + parameter.Value);
        }

        return sb.ToString();
    }

This bit of code is responsible for building the URL that allows us to ask the user for access.

With the code that is returned to our return URL we call this bit of code:

    public void RequestAccessToken(string code)
    {
        var dictionary = new Dictionary<string, string>
        {
            {"code", code},
            {"client_id", Tokens.ConsumerKey},
            {"client_secret", Tokens.ConsumerSecret},
            {"redirect_uri", callbackUrl},
            {"grant_type", "authorization_code"}
        };
        var parameters = NormalizeParameters(dictionary);

        var resultString = "";

        using (var wc = new WebClient())
        {
            //wc.Headers[HttpRequestHeader.Host] = "POST";
            wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
            resultString = wc.UploadString(requestAccessTokenUrl, parameters);
        }

        var json = JObject.Parse(resultString);

        Tokens.AccessToken = json["access_token"].ToString();
        Tokens.RefreshToken = (json["refresh_token"] != null) ? json["refresh_token"].ToString() : null;
        Tokens.Save(companyId);
    }

Now because we have declared our app as offline, when we do any api calls we can just use this bit of code:

    public bool CheckAccessToken()
    {
        try
        {
            RefreshToken(); // Get and store our new tokens

            return true;
        }
        catch
        {
            return false;
        }
    }

    private void RefreshToken()
    {
        var dictionary = new Dictionary<string, string>
        {
            {"refresh_token", Tokens.RefreshToken},
            {"client_id", Tokens.ConsumerKey},
            {"client_secret", Tokens.ConsumerSecret},
            {"grant_type", "refresh_token"}
        };
        var parameters = NormalizeParameters(dictionary);

        var resultString = "";

        using (var wc = new WebClient())
        {
            //wc.Headers[HttpRequestHeader.Host] = "POST";
            wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
            resultString = wc.UploadString(requestAccessTokenUrl, parameters);
        }

        var json = JObject.Parse(resultString);

        Tokens.AccessToken = json["access_token"].ToString();
        Tokens.Save(companyId);
    }

In my windows service I have this code:

    public void SynchroniseAssets(IEnumerable<SynchronisedAsset> assets)
    {
        if (api.CheckAccessToken())
        {
            foreach (var asset in assets)
            {
                var uploadAssetThread = new Thread(() => CreateUploadRequest(asset));
                uploadAssetThread.Start(); // Upload our assets at the same time
            }
        }
    }

which as you can see calls the Original code above. The error which I am getting when I parse it into json is this:

{
   "error":{
      "errors":[
     {
        "domain":"youtube.header",
        "reason":"youtubeSignupRequired",
        "message":"Unauthorized",
        "locationType":"header",
        "location":"Authorization"
     }
      ],
      "code":401,
      "message":"Unauthorized"
   }
}

So, if anyone could help me work out what that means, that would be great.

Cheers,

/r3plica

回答1:

The reason "youtubeSignupRequired" usually means the YouTube account has not been set up properly or you have not created your YouTube channel yet. This is a requirement before you can upload videos. It would be great if Google could improve their error message for this case and make it a bit more verbose.



回答2:

The issue returning 401 Unautorized was because the account I was gaining access to did not have a youtube account associated with it.