Box API всегда возвращает недопустимый параметр grant_type при получении токена доступа

Я пишу свой собственный Box SDK для WP8, чтобы максимально использовать возможности задач. У меня проблемы с получением токена доступа. Я всегда получаю это в качестве возврата:

{"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"}

Код (весь на C #), который я использую:

    internal const String TokenURL = "https://api.box.com/oauth2/token";

CloudHttpAsync.DownloadResponceStreamAsync
(
    CloudHttpAsync.PostAsync
    (
        TokenURL,
        new MemoryStream
        (
            UTF8Encoding.UTF8.GetBytes
            (
                HttpUtility.UrlEncode
                (
                    String.Format
                    (
                        "grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}&redirect_uri={3}",
                        Code,
                        ClientID,
                        ClientSecret,
                        RedirectURI
                    )
                )
            )
        ),
        null,
        null
    ),
    null
).ContinueWith((AsyncStream) =>
    {
        try
        {
            if (AsyncStream.Exception != null)
            {
                TaskSource.TrySetException(AsyncStream.Exception.InnerExceptions);
                return;
            }

            String Result = "";
            using (StreamReader Reader = new StreamReader(AsyncStream.Result))
            {
                Result = Reader.ReadToEnd();
            }

            BoxAuthToken Token = JsonConvert.DeserializeObject<BoxAuthToken>(Result);
            TaskSource.TrySetResult(Token);
        }
        catch (Exception e)
        {
            TaskSource.TrySetException(e);
        }
    });

а также

    public static Task<HttpWebResponse> PostAsync(String URL, Stream UploadData, IRequestSigner Signer, IProgress<NetworkProgress> Progress)
    {
        TaskCompletionSource<HttpWebResponse> TaskSource = new TaskCompletionSource<HttpWebResponse>();

        HttpWebRequest Request = WebRequest.CreateHttp(URL);
        Request.Method = "POST";

        if (Signer != null)
        {
            Signer.Sign(Request).ContinueWith((o) =>
            {
                if (o.Exception != null)
                {
                    TaskSource.TrySetException(o.Exception.InnerExceptions);
                    return;
                }

                UploadDataAsync(Request, UploadData, Progress).ContinueWith((AsyncRequest) =>
                {
                    if (AsyncRequest.Exception != null)
                    {
                        TaskSource.TrySetException(AsyncRequest.Exception.InnerExceptions);
                        return;
                    }

                    GetResponceAsync(Request).ContinueWith((AsyncResponce) =>
                    {
                        if (AsyncResponce.Exception != null)
                        {
                            TaskSource.TrySetException(AsyncResponce.Exception.InnerExceptions);
                            return;
                        }

                        TaskSource.TrySetResult(AsyncResponce.Result);
                    });
                });
            });
        }
        else
        {
            UploadDataAsync(Request, UploadData, Progress).ContinueWith((AsyncRequest) =>
            {
                if (AsyncRequest.Exception != null)
                {
                    TaskSource.TrySetException(AsyncRequest.Exception.InnerExceptions);
                    return;
                }

                GetResponceAsync(Request).ContinueWith((AsyncResponce) =>
                {
                    if (AsyncResponce.Exception != null)
                    {
                        TaskSource.TrySetException(AsyncResponce.Exception.InnerExceptions);
                        return;
                    }

                    TaskSource.TrySetResult(AsyncResponce.Result);
                });
            });
        }

        return TaskSource.Task;
    }

    internal static Task<HttpWebRequest> UploadDataAsync(HttpWebRequest Request, Stream Data, IProgress<NetworkProgress> Progress)
    {
        TaskCompletionSource<HttpWebRequest> TaskSource = new TaskCompletionSource<HttpWebRequest>();

        if (Data.Length != 0)
        {
            Request.ContentLength = Data.Length;
            Request.AllowWriteStreamBuffering = false;

            Request.BeginGetRequestStream(new AsyncCallback((IAR) =>
                {
                    try
                    {
                        using (Stream UploadStream = Request.EndGetRequestStream(IAR))
                        {
                            Int64 Upload = 0;
                            Int64 TotalUploaded = 0;
                            Int64 Total = Data.Length;
                            Byte[] Buffer = new Byte[4096];

                            while (TotalUploaded < Total)
                            {
                                Upload = Data.Read(Buffer, 0, Buffer.Length);
                                TotalUploaded += Upload;
                                UploadStream.Write(Buffer, 0, (Int32)Upload);

                                if (Progress != null)
                                {
                                    Progress.Report(new NetworkProgress()
                                    {
                                        Operation = NetworkOperation.Uploading,
                                        TotalBytes = Total,
                                        BytesProcessed = TotalUploaded
                                    });
                                }
                            }
                        }

                        TaskSource.TrySetResult(Request);
                    }
                    catch (Exception e)
                    {
                        TaskSource.TrySetException(e);
                    }
                }),
                null);
        }
        else
        {
            TaskSource.TrySetResult(Request);
        }

        return TaskSource.Task;
    }

    internal static Task<HttpWebResponse> GetResponceAsync(HttpWebRequest Request)
    {
        TaskCompletionSource<HttpWebResponse> TaskSource = new TaskCompletionSource<HttpWebResponse>();

        Request.BeginGetResponse(new AsyncCallback((IAR) =>
            {
                try
                {
                    HttpWebResponse Responce = (HttpWebResponse)Request.EndGetResponse(IAR);
                    TaskSource.TrySetResult(Responce);
                }
                catch (Exception e)
                {
                    if (e is WebException && (e as WebException).Response.ContentLength > 0)
                    {
                        TaskSource.TrySetResult((HttpWebResponse)(e as WebException).Response);
                    }
                    else
                    {
                        TaskSource.TrySetException(e);
                    }
                }
            }),
            null);

        return TaskSource.Task;
    }

    public static Task<StreamAndLength> GetResponceStreamAsync(Task<HttpWebResponse> Task)
    {
        TaskCompletionSource<StreamAndLength> TaskSource = new TaskCompletionSource<StreamAndLength>();

        Task.ContinueWith((AsyncHWR) =>
            {
                if (AsyncHWR.Exception != null)
                {
                    TaskSource.TrySetException(AsyncHWR.Exception.InnerExceptions);
                    return;
                }

                HttpWebResponse Responce = AsyncHWR.Result;
                TaskSource.TrySetResult( new StreamAndLength() { Stream = Responce.GetResponseStream(), Length = Responce.ContentLength });
            });

        return TaskSource.Task;
    }

    public static Task<MemoryStream> DownloadResponceStreamAsync(Task<HttpWebResponse> Task, IProgress<NetworkProgress> Progress)
    {
        TaskCompletionSource<MemoryStream> TaskSource = new TaskCompletionSource<MemoryStream>();

        GetResponceStreamAsync(Task).ContinueWith((AsyncStream) =>
            {
                if (AsyncStream.Exception != null)
                {
                    TaskSource.TrySetException(AsyncStream.Exception.InnerExceptions);
                    return;
                }

                MemoryStream MemStream = new MemoryStream();
                MemStream.SetLength(AsyncStream.Result.Length);

                Int64 CurrentRead = 0;
                Int64 TotalRead = 0;
                Int64 Total = AsyncStream.Result.Length;
                Byte[] Buffer = new Byte[4096];

                using (Stream DownloadStream = AsyncStream.Result.Stream)
                while (TotalRead < Total)
                {
                    CurrentRead = DownloadStream.Read(Buffer, 0, Buffer.Length);
                    MemStream.Write(Buffer, 0, (Int32)CurrentRead);
                    TotalRead += CurrentRead;

                    if (Progress != null)
                    {
                        Progress.Report(new NetworkProgress()
                        {
                            Operation = NetworkOperation.Downloading,
                            TotalBytes = Total,
                            BytesProcessed = TotalRead
                        });
                    }
                }

                MemStream.Position = 0;
                TaskSource.TrySetResult(MemStream);
            });

        return TaskSource.Task;
    }

    internal class StreamAndLength
    {
        public Stream Stream { get; set; }
        public Int64 Length { get; set; }
    }

К сожалению, кода много, я люблю писать в общих чертах :)

Изменить: необработанные ответы (ClientID и Client Secret удалены)

При кодировании URL каждое значение:

POST https://api.box.com/oauth2/token HTTP/1.1
Accept: */*
Content-Length: 196
Accept-Encoding: identity
User-Agent: NativeHost
Host: api.box.com
Connection: Keep-Alive
Cache-Control: no-cache

grant_type=authorization_code&code=JknaLbfT6lAXmey3FLYrp9eg1jMbpFuQ&client_id=[subbed]&client_secret=[subbed]&redirect_uri=https%3a%2f%2fCloudBoxWP8

Возвращение:

HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 01 Mar 2013 07:35:22 GMT
Content-Type: application/json
Connection: keep-alive
Set-Cookie: box_visitor_id=51305a3a187f34.52738262; expires=Sat, 01-Mar-2014 07:35:22 GMT; path=/; domain=.box.com
Set-Cookie: country_code=US; expires=Tue, 30-Apr-2013 07:35:22 GMT; path=/
Cache-Control: no-store
Content-Length: 99

{"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"}

Когда URL кодирует всю строку:

POST https://api.box.com/oauth2/token HTTP/1.1
Accept: */*
Content-Length: 214
Accept-Encoding: identity
User-Agent: NativeHost
Host: api.box.com
Connection: Keep-Alive
Cache-Control: no-cache

grant_type%3dauthorization_code%26code%3d3ikruv5elfdw3fOP55aMDSX7ybLqBFlA%26client_id%3d[subbed]%26client_secret%3d[subbed]%26redirect_uri%3dhttps%3a%2f%2fCloudBoxWP8

Возвращение

HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 01 Mar 2013 07:46:03 GMT
Content-Type: application/json
Connection: keep-alive
Set-Cookie: box_visitor_id=51305cbb339de4.03221876; expires=Sat, 01-Mar-2014 07:46:03 GMT; path=/; domain=.box.com
Set-Cookie: country_code=US; expires=Tue, 30-Apr-2013 07:46:03 GMT; path=/
Cache-Control: no-store
Content-Length: 99

{"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"}

Без кодировки URL:

POST https://api.box.com/oauth2/token HTTP/1.1
Accept: */*
Content-Length: 190
Accept-Encoding: identity
User-Agent: NativeHost
Host: api.box.com
Connection: Keep-Alive
Cache-Control: no-cache

grant_type=authorization_code&code=2wgIzfqhvIgRtVIp2ZvqZ9X8R5u0QNaf&client_id=[subbed]&client_secret=[subbed]&redirect_uri=https://CloudBoxWP8

Возвращение:

HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 01 Mar 2013 07:50:31 GMT
Content-Type: application/json
Connection: keep-alive
Set-Cookie: box_visitor_id=51305dc751d7f5.67064854; expires=Sat, 01-Mar-2014 07:50:31 GMT; path=/; domain=.box.com
Set-Cookie: country_code=US; expires=Tue, 30-Apr-2013 07:50:31 GMT; path=/
Cache-Control: no-store
Content-Length: 99

{"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"}

person Tom Myles    schedule 27.02.2013    source источник
comment
Том, не могли бы вы опубликовать Fiddler трассировку HTTP-запроса, отправляемого в Box?   -  person John Hoerr    schedule 27.02.2013
comment
Кроме того, кодируете ли вы URL-адрес uri перенаправления?   -  person seanrose    schedule 28.02.2013
comment
Просто попробовал только URL, кодирующий адрес перенаправления с тем же результатом. Теперь, используя скрипач, я попытался использовать его для отправки POST, используя то, что должно быть действительным телом запроса, он также возвращает ошибку no grant_type. Попробуя снова, я сделал запрос POST к api.box.com/oauth2/token с телом запроса grant_type = authorization_code, но я все равно получаю ошибку no grant_type!   -  person Tom Myles    schedule 28.02.2013
comment
Можете ли вы опубликовать необработанные данные запроса и ответа (с удаленной секретной информацией)?   -  person John Hoerr    schedule 28.02.2013
comment
Готово, надеюсь, это поможет   -  person Tom Myles    schedule 01.03.2013
comment
Привет, Том, как у тебя дела с этим? Вам нужна дополнительная помощь?   -  person John Hoerr    schedule 06.03.2013


Ответы (4)


Его нигде нет в документации Box API, но для запроса на получение токена доступа требуется заголовок Content-Type: application/x-www-form-urlencoded

Я тоже некоторое время застревал в этой части, пока не нашел ответ на StackOverflow. Я забыл ссылку на него.

person rotinegg    schedule 24.05.2013

Запрос / ответ были бы полезны. Похоже, вы UrlEncoding всю строку запроса, а не только каждое значение. Что будет отправлено нам как: grant_type% 3Dauthorization_code% 26code% 3Dxyz% 26client_id% 3Dxyz% 26client_secret% 3Dxyz% 26redirect_uri% 3Dxyz

Вместо: grant_type = authorization_code & code = xyz & client_id = xyz & client_secret = xyz & redirect_uri = xyz

person John Huffaker    schedule 28.02.2013
comment
Просто попытался, чтобы это не повлияло. Я разместил необработанные данные запроса и ответа, чтобы вы могли увидеть себя - person Tom Myles; 01.03.2013

Я думаю, что включение redirect_uri в тело запроса может усложнить ситуацию, особенно потому, что для него установлено недопустимое значение (https://CloudBoxWP8). Вы можете решить эту проблему, настроив приложение для обработки пользовательского протокола (cloudboxwp8://) и предварительно настроив Box чтобы перенаправить на это, когда токен предоставлен.

  1. Зарегистрируйте собственный протокол для вашего приложения WP8. Например, cloudboxwp8.
  2. Дополните свое приложение WP8 обработкой запроса какой-либо конечной точки по этому протоколу. Например, cloudboxwp8://tokengranted. Реализуйте здесь свою логику обработки токенов.
  3. Отредактируйте свое приложение Box и перейдите в раздел параметров OAuth2 (через Управление приложением Box => Изменить приложение )
  4. В поле redirect_uri установите значение для настраиваемого протокола и конечной точки из шага 2. Сохраните изменения.
  5. Удалите redirect_uri из тела запроса и повторите попытку.
person John Hoerr    schedule 04.03.2013
comment
Я был немного занят последние несколько дней и буду до выходных. Тогда я попробую это. Чтобы указать, я могу выполнить часть входа в систему с помощью элемента управления WebBrowser, который появляется в приложении (так что библиотека, которую я пишу, полностью автономна), этот метод отлично работал с другими реализациями OAuth, такими как Dropbox. Извините за задержку с ответом! - person Tom Myles; 12.03.2013

в Windows phone 8.1 WinRT

Dictionary<string, string> contentList = new Dictionary<string, string>();

contentList.Add("code", code);
contentList.Add("client_id", client_id);
contentList.Add("client_secret", clientSecret);
contentList.Add("redirect_uri", redirectUri);
contentList.Add("grant_type", "authorization_code");

FormUrlEncodedContent content = new FormUrlEncodedContent(contentList);

var response = await client.PostAsync(baseURL, content);
YouTubeAutenticationResponse res =       JsonConvert.DeserializeObject<YouTubeAutenticationResponse>(await     response.Content.ReadAsStringAsync());

public class YouTubeAutenticationResponse
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    [JsonProperty("token_type")]
    public string TokenType { get; set; }
    [JsonProperty("expires_in")]
    public string ExpiresIn { get; set; }
    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
}
person Dživo Jelić    schedule 31.08.2014