An oauth server error was encountered that did not contain a json body

I have been attempting to develop an API and client which communicate to each other via an implementation of ThePHPLeague's OAuth2 server and client. Using the curl command in a CLI, I am able to

I have been attempting to develop an API and client which communicate to each other via an implementation of ThePHPLeague’s OAuth2 server and client. Using the curl command in a CLI, I am able to generate a token and use it to gain access to protected resources.

User authentication relies on a bespoke PHP solution with Slim framework, which accepts a username and encrypted password stored in a database table. The same table is used for the OAuth2 implementation’s user management.

When a user login attempt is successfully validated, the AbstractProvider ‘s getAccessToken() method is called and an access token is requested from the API. Here is where the problem lies.

I have tested functionality using the GenericProvider class. I’ve also extended the provider to create my own class. Using the both providers, I see the following error when I attempt to login:

Slim Application Error
Type: UnexpectedValueException
Code: 0
Message: An OAuth server error was encountered that did not contain a JSON body
File: /var/www/sloth2-client-php/vendor/league/oauth2-client/src/Provider/AbstractProvider.php
Line: 693

#0 /.../vendor/league/oauth2-client/src/Provider/AbstractProvider.php(626): 
LeagueOAuth2ClientProviderAbstractProvider->parseResponse(Object(GuzzleHttpPsr7Response))
#1 /.../src/SlothProvider.php(113): LeagueOAuth2ClientProviderAbstractProvider->getParsedResponse(Object(GuzzleHttpPsr7Request))
#2 /.../src/Controller/AuthenticationController.php(69): AppSlothProvider->getAccessToken(Object(LeagueOAuth2ClientGrantClientCredentials))
#3 /.../vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php(42): AppControllerAuthenticationController->authenticate(Object(SlimPsr7Request), Object(SlimPsr7Response), Array)
#4 /.../vendor/slim/slim/Slim/Routing/Route.php(372): SlimHandlersStrategiesRequestResponse->__invoke(Array, Object(SlimPsr7Request), Object(SlimPsr7Response), Array)
#5 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): SlimRoutingRoute->handle(Object(SlimPsr7Request))
#6 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): SlimMiddlewareDispatcher->handle(Object(SlimPsr7Request))
#7 /.../vendor/slim/slim/Slim/Routing/Route.php(333): SlimMiddlewareDispatcher->handle(Object(SlimPsr7Request))
#8 /.../vendor/slim/slim/Slim/Routing/RouteRunner.php(65): SlimRoutingRoute->run(Object(SlimPsr7Request))
#9 /.../vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php(58): SlimRoutingRouteRunner->handle(Object(SlimPsr7Request))
#10 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(132): SlimMiddlewareRoutingMiddleware->process(Object(SlimPsr7Request), Object(SlimRoutingRouteRunner))
#11 /.../vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(89): class@anonymous->handle(Object(SlimPsr7Request))
#12 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(132): SlimMiddlewareErrorMiddleware->process(Object(SlimPsr7Request), Object(class@anonymous))
#13 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): class@anonymous->handle(Object(SlimPsr7Request))
#14 /.../vendor/slim/slim/Slim/App.php(206): SlimMiddlewareDispatcher->handle(Object(SlimPsr7Request))
#15 /.../vendor/slim/slim/Slim/App.php(190): SlimApp->handle(Object(SlimPsr7Request))
#16 /.../public/index.php(8): SlimApp->run()
#17 {main}

The SlothProvider class mentioned in the stack trace is as follows:

<?php

namespace App;

use LeagueOAuth2ClientProviderAbstractProvider;
use LeagueOAuth2ClientTokenAccessToken;
use LeagueOAuth2ClientToolBearerAuthorizationTrait;
use PsrHttpMessageResponseInterface;
use UnexpectedValueException;

class SlothProvider extends AbstractProvider
{
  use BearerAuthorizationTrait;

  public function __construct()
  {
    $this->clientId = getenv('OAUTH2_CLIENT_ID');
    $this->clientSecret = getenv('OAUTH2_CLIENT_SECRET');
    $this->redirectUri = getenv('OAUTH2_REDIRECT_URI');
  }

  /**
   * Get authorization url to begin OAuth flow
   *
   * @return string
   */
  public function getBaseAuthorizationUrl()
  {
    return getenv('OAUTH2_AUTHORIZATION_URL');
  }

  /**
   * Get access token url to retrieve token
   *
   * @param  array $params
   *
   * @return string
   */
  public function getBaseAccessTokenUrl(array $params)
  {
    return getenv('OAUTH2_ACCESS_TOKEN_URL');
  }

  /**
   * Get provider url to fetch user details
   *
   * @param  AccessToken $token
   *
   * @return string
   */
  public function getResourceOwnerDetailsUrl(AccessToken $token)
  {
    // You don't have one. You might consider throwing an exception here so
    // that, when this is called, you get an error and can code your
    // application to ensure that nothing calls this.
    //
    // Note that $this->getResourceOwner() is the most likely culprit for
    // calling this. Just don't call getResourceOwner() in your code.
  }

  /**
   * Get the default scopes used by this provider.
   *
   * This should not be a complete list of all scopes, but the minimum
   * required for the provider user interface!
   *
   * @return array
   */
  protected function getDefaultScopes()
  {
    return ['basic'];
  }

  /**
   * Check a provider response for errors.
   *
   * @throws IdentityProviderException
   * @param  ResponseInterface $response
   * @param  array $data Parsed response data
   * @return void
   */
  protected function checkResponse(ResponseInterface $response, $data)
  {
    // Write code here that checks the response for errors and throws
    // an exception if you find any.
  }

  /**
   * Generate a user object from a successful user details request.
   *
   * @param array $response
   * @param AccessToken $token
   * @return LeagueOAuth2ClientProviderResourceOwnerInterface
   */
  protected function createResourceOwner(array $response, AccessToken $token)
  {
    // Leave empty. You can't use this, since you don't have a clear
    // resource owner details URL. You might consider throwing an
    // exception from here, as well. See note on
    // getResourceOwnerDetailsUrl() above.
  }

  /**
   * Requests an access token using a specified grant and option set.
   *
   * @param  mixed $grant
   * @param  array $options
   * @throws IdentityProviderException
   * @return AccessTokenInterface
   */
  public function getAccessToken($grant, array $options = [])
  {
    $grant = $this->verifyGrant($grant);
    $params = [
      'client_id'     => $this->clientId,
      'client_secret' => $this->clientSecret,
      'redirect_uri'  => $this->redirectUri,
    ];
    $params   = $grant->prepareRequestParameters($params, $options);
    $request  = $this->getAccessTokenRequest($params);
    $response = $this->getParsedResponse($request);
    if (false === is_array($response)) {
      throw new UnexpectedValueException(
        'Invalid response received from Authorization Server. Expected JSON.'
      );
    }
    $prepared = $this->prepareAccessTokenResponse($response);
    $token    = $this->createAccessToken($prepared, $grant);
    return $token;
  }
}

I would like to know what this error means and how to solve it.

As this issue is still open, I’d like to raise some concern and share my thought.
This is how it is currently implemented

public function getParsedResponse(RequestInterface $request)
{
    try {
        $response = $this->getResponse($request);
    } catch (BadResponseException $e) {
        $response = $e->getResponse();
    }

    $parsed = $this->parseResponse($response);

    $this->checkResponse($response, $parsed);

    return $parsed;
}

I assume the intended task of parseResponse() is to correctly parse the response body and return it. Currently, parseResponse is able to parse urlencoded or valid json body. In general, the result of a json parsing does not need to be an array. It could even be null, empty string, integer, etc. So the value of $parsed can be of different type, which I don’t really care much. It is up to the calling function to correctly handle that value.

/**
 * Parses the response according to its content-type header.
 *
 * @throws UnexpectedValueException
 * @param  ResponseInterface $response
 * @return array
 */
protected function parseResponse(ResponseInterface $response)
{
    $content = (string) $response->getBody();
    $type = $this->getContentType($response);

    if (strpos($type, 'urlencoded') !== false) {
        parse_str($content, $parsed);
        return $parsed;
    }

    // Attempt to parse the string as JSON regardless of content type,
    // since some providers use non-standard content types. Only throw an
    // exception if the JSON could not be parsed when it was expected to.
    try {
        return $this->parseJson($content);
    } catch (UnexpectedValueException $e) {
        if (strpos($type, 'json') !== false) {
            throw $e;
        }

        if ($response->getStatusCode() == 500) {
            throw new UnexpectedValueException(
                'An OAuth server error was encountered that did not contain a JSON body',
                0,
                $e
            );
        }

        return $content;
    }
}

The @return array type hint is surely wrong.

The problem with current implementation of parseResponse() is that it throws an exception which does not help much in the calling function if the response body cannot be parsed as JSON. This includes body having empty string as value.
Thus, I’d suggest to throw a new exception type called ResponseParsingException which encapsulates the response and its body, so that the calling function can reacts accordingly by retrieving the response and/or the body from the catched exception.
To ensure BC we can define a flag in AbstractProvider, e.g.

protected $mayThrowResponseParsingException = false;

which can be used to control the behavior of parseResponse().

 /**
 * Parses the response according to its content-type header.
 *
 * @throws UnexpectedValueException
 * @throws ResponseParsingException if the flag $mayThrowResponseParsingException is true and
 *                                  response body cannot be parsed.
 * @param  ResponseInterface $response
 * @return array|string
 */
protected function parseResponse(ResponseInterface $response)
{
    $content = (string) $response->getBody();
    $type = $this->getContentType($response);

    if (strpos($type, 'urlencoded') !== false) {
        parse_str($content, $parsed);
        return $parsed;
    }

    // Attempt to parse the string as JSON regardless of content type,
    // since some providers use non-standard content types. Only throw an
    // exception if the JSON could not be parsed when it was expected to.
    try {
        return $this->parseJson($content);
    } catch (UnexpectedValueException $e) {
        if (strpos($type, 'json') !== false) {
            throw $this->mayThrowResponseParsingException
                ? new ResponseParsingException($response, $content, $e->getMessage(), $e->getCode())
                : $e;
        }

        // for any other content types
        if ($this->mayThrowResponseParsingException) {
            // let the calling function decide what to do with the response and its body
            throw new ResponseParsingException($response, $content, '', 0);
        } else {
            if ($response->getStatusCode() == 500) {
                throw new UnexpectedValueException(
                    'An OAuth server error was encountered that did not contain a JSON body',
                    0,
                    $e
                );
            }

            return $content;
        }
    }
}

`
What do you think?
I know that the method checkResponse() can be (mis)used to re-parse the response later. But this is most probably not what this method is supposed to do. I assume that checkResponse should be used to check for error messages in the successfully parsed body returned by a well-behaved identity provider.

  1. Error description
  2. Short error description in the response
  3. Example of an error message

If an error occurs, the request processing stops, and the server returns an HTTP response code that identifies the error. In addition to the code, the response contains a short error description.

The error message is returned in the format specified in the request URL after the method name or in the Accept HTTP header.

The error description is passed in the error parameter. This parameter contains the error code (the code parameter) and a short error description (the message parameter).

Code

Name

Explanation

200

OK

The request is successfully completed.

206

Partial Content

The request is partially completed.

400

Bad Request

The request is invalid.

401

Unauthorized

The request doesn’t include authorization data.

403

Forbidden

Incorrect authorization data is specified in the request, or access to the requested resource is denied.

404

Not Found

The requested resource isn’t found.

405

Method Not Allowed

The requested method isn’t supported for the specified resource.

415

Unsupported Media Type

The requested content type isn’t supported by the method.

420

Enhance Your Calm

The resource access restriction is exceeded.

500

Internal Server Error

Internal server error. Try calling the method after a while. If the error persists, contact the Yandex.Market support service.

503

Service Unavailable

The server is temporarily unavailable due to high load. Try calling the method after a while.

  • For the 400 Bad Request error:

    Description

    Explanation

    Possible solution

    Collection of field must not be empty

    The parameter must not be empty.

    Specify at least one element for the parameter.

    Invalid status: 'status'

    Invalid status is specified.

    Check if the sent status is correct for order filtering by status.

    JSON: {message}

    The JSON data format contains an error.

    Check if the data passed in the request body has the correct JSON format.

    Missing field

    The required parameter isn’t specified.

    Specify a value for the required parameter.

    The request is too big

    The HTTP request size limit is exceeded.

    Cut the request size by reducing the amount of the sent data.

    Too long time period. Maximum is 'maxPeriod' days

    The specified date range is too large. Maximum range — maxPeriod.

    Reduce the date range to filter orders by date.

    Unexpected character 'character': expected a valid value 'values'

    Invalid character.

    Check the request body encoding. The required encoding is UTF-8.

    Unexpected end of content

    The request body ends unexpectedly.

    Check if the data passed in the request body has the correct format.

    Value / length of field (value) must be between min and max [exclusively]

    The parameter value (length) must be between the min and max values and not equal to them.

    Check if the parameter value is correct.

    Value / length of field (value) must be greater / less than [or equal to] limit

    The parameter value (length) must be equal to or greater than (less than) the specified limit value.

    Check if the parameter value is correct.

    Value of field has too high scale: 'price'

    The accuracy of the parameter is set too high.

    Set the parameter values with less precision.

    Value of field must match the pattern: 'regExp'

    The parameter value must match the regular expression.

    Check if the parameter value is correct.

    XML: {message}

    The XML data format contains an error.

    Check if the data passed in the request body has the correct XML format.

    Other short descriptions that can be found in messages about this error are provided in the descriptions of the corresponding resources.

  • For the 401 Unauthorized error:

    Description

    Explanation

    Possible solution

    Unsupported authorization type specified in Authorization header

    Authorization type passed in the Authorization HTTP header isn’t supported.

    Check if the authorization data is correct.

    Authorization header has invalid syntax

    The Authorization HTTP header format is incorrect.

    Check if the authorization data is correct.

    OAuth credentials are not specified

    The request doesn’t include authorization data.

    Check that the authorization data is correct.

    OAuth token is not specified

    The request doesn’t include the authorization token (the oauth_token parameter).

    Check if the authorization data is correct.

    OAuth client id is not specified

    The request doesn’t include the application ID (the oauth_client_id parameter).

    Check if the authorization data is correct.

  • For the 403 Forbidden error:

    Description

    Explanation

    Possible solution

    Access denied

    Access to the specified resource is prohibited.

    Check if the resource is specified correctly, and if the authorized user login has access to it.

    Access to API denied for the client / campaign

    The client or store isn’t allowed to access the Yandex.Market Partner API.

    Agency clients should contact their agency about getting access to the Yandex.Market Partner API.

    Client id is invalid

    The specified application ID (the oauth_client_id parameter) is invalid.

    Check if the authorization data is correct. If they are correct, get a new app ID, repeat the request with the new authorization data.

    Scope is invalid

    The specified authorization token (the oauth_token parameter) doesn’t have the necessary set of rights.

    Get a new authorization token, mention the right to use the Yandex.Market Partner API when you receive it, and repeat the request with the new authorization data.

    Token is invalid

    The specified authorization token (parameter oauth_token) is invalid.

    Check if the authorization data is correct. If they are correct, get a new authorization token, repeat the request with the new authorization data.

    User account is disabled

    The user account for which the specified authorization token was issued is blocked.

    Contact the Yandex.Market support service.

  • For the 404 Not Found error:

    Description

    Explanation

    Possible solution

    Feed not found: 'feedId'

    The price list specified in the request isn’t found.

    Check if the sent price list ID is correct.

    Login not found: 'login'

    The username specified in the request isn’t found.

    Check if the sent username is correct.

    Model not found: 'modelId'

    The model specified in the request isn’t found.

    Check if the model ID you are passing is correct.

  • For the 405 Method Not Allowed error:

    Description

    Explanation

    Possible solution

    Request method 'method' not supported

    The requested HTTP method isn’t supported.

    Check the methods supported by the resource. You can find the list of methods in the Requests reference section.

  • For the 415 Unsupported Media Type error:

    Description

    Explanation

    Possible solution

    Content type 'content-type' not supported

    The requested content type isn’t supported.

    Pass one of the supported content types.

    Missing Content-Type

    The content type isn’t specified.

    Pass the content type.

    Unknown content-type: 'content-type'

    The requested content type is unknown.

    Pass one of the supported content types.

  • For the 420 Enhance Your Calm error:

    Description

    Explanation

    Possible solution

    Hit rate limit of 'N' parallel requests

    Exceeded the global limit on the number of simultaneous requests to the Yandex.Market Partner API.

    Reduce the number of concurrent requests to the partner API within a single store or partner to N requests.

    Hit rate limit of 'N' requests per 'period' for resource 'R'

    The resource restriction for the N number of requests to the R resource over the period for the same store or partner is exceeded.

    The time until which the limit applies is specified in the X-RateLimit-Resource-Until header. You can use of the resource after the specified time.

  • For the 503 Service Unavailable error:

    Description

    Explanation

    Possible solution

    Service temporarily unavailable. Please, try again later

    The server is temporarily unavailable due to high load.

    Try repeating the request after a while.

Request example:

GET /v2/campaigns.xml HTTP/1.1
Host: api.partner.market.yandex.ru
Accept: */*
Authorization: OAuth oauth_token=,oauth_client_id=b12320932d4e401ab6e1ba43d553d433

Response example:

<response>
  <errors>
    <error code="UNAUTHORIZED" message="OAuth token is not specified"/>
  </errors>
  <error code="401">
    <message>OAuth token is not specified</message>
  </error>
</response>

Request example:

GET /v2/campaigns.json HTTP/1.1
Host: api.partner.market.yandex.ru
Accept: */*
Authorization: OAuth oauth_token=,oauth_client_id=b12320932d4e401ab6e1ba43d553d433

Response example:

{
  "errors":
  [
    {
      "code": "UNAUTHORIZED",
      "message": "OAuth token is not specified"
    }
  ],
  "error":
  {
    "code": 401,
    "message": "OAuth token is not specified"
  }
}

Логотип OAuth 2.0

На хабре уже писали про OAuth 1.0, но понятного объяснения того, что такое OAuth 2.0 не было. Ниже я расскажу, в чем отличия и преимущества OAuth 2.0 и, как его лучше использовать на сайтах, в мобильных и desktop-приложениях.

Что такое OAuth 2.0

OAuth 2.0 — протокол авторизации, позволяющий выдать одному сервису (приложению) права на доступ к ресурсам пользователя на другом сервисе. Протокол избавляет от необходимости доверять приложению логин и пароль, а также позволяет выдавать ограниченный набор прав, а не все сразу.

Чем отличаются OpenID и OAuth

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

OpenID предназначен для аутентификации — то есть для того, чтобы понять, что этот конкретный пользователь является тем, кем представляется. Например, с помощью OpenID некий сервис Ололо может понять, что зашедший туда пользователь, это именно Рома Новиков с Mail.Ru. При следующей аутентификации Ололо сможет его опять узнать и понять, что, это тот же Рома, что и в прошлый раз.

OAuth же является протоколом авторизации, то есть позволяет выдать права на действия, которые сам Ололо сможет производить в Mail.Ru от лица Ромы. При этом Рома после авторизации может вообще не участвовать в процессе выполнения действий, например, Ололо сможет самостоятельно заливать фотографии на Ромин аккаунт.

Как работает OAuth 2.0

Как и первая версия, OAuth 2.0 основан на использовании базовых веб-технологий: HTTP-запросах, редиректах и т. п. Поэтому использование OAuth возможно на любой платформе с доступом к интернету и браузеру: на сайтах, в мобильных и desktop-приложениях, плагинах для браузеров…

Ключевое отличие от OAuth 1.0 — простота. В новой версии нет громоздких схем подписи, сокращено количество запросов, необходимых для авторизации.

Общая схема работы приложения, использующего OAuth, такова:

  1. получение авторизации
  2. обращение к защищенным ресурсам

Результатом авторизации является access token — некий ключ (обычно просто набор символов), предъявление которого является пропуском к защищенным ресурсам. Обращение к ним в самом простом случае происходит по HTTPS с указанием в заголовках или в качестве одного из параметров полученного access token‘а.

В протоколе описано несколько вариантов авторизации, подходящих для различных ситуаций:

  • авторизация для приложений, имеющих серверную часть (чаще всего, это сайты и веб-приложения)
  • авторизация для полностью клиентских приложений (мобильные и desktop-приложения)
  • авторизация по логину и паролю
  • восстановление предыдущей авторизации

Авторизация для приложений, имеющих серверную часть

Схема авторизации приложений, имеющих серверную часть

  1. Редирект на страницу авторизации
  2. На странице авторизации у пользователя запрашивается подтверждение выдачи прав
  3. В случае согласия пользователя, браузер редиректится на URL, указанный при открытии страницы авторизации, с добавлением в GET-параметры специального ключа — authorization code
  4. Сервер приложения выполняет POST-запрос с полученным authorization code в качестве параметра. В результате этого запроса возвращается access token

Это самый сложный вариант авторизации, но только он позволяет сервису однозначно установить приложение, обращающееся за авторизацией (это происходит при коммуникации между серверами на последнем шаге). Во всех остальных вариантах авторизация происходит полностью на клиенте и по понятным причинам возможна маскировка одного приложения под другое. Это стоит учитывать при внедрении OAuth-аутентификации в API сервисов.

Пример

Здесь и далее примеры приводятся для API Mail.Ru, но логика одинаковая для всех сервисов, меняются только адреса страниц авторизации. Обратите внимание, что запросы надо делать по HTTPS.

Редиректим браузер пользователя на страницу авторизации:

> GET /oauth/authorize?response_type=code&client_id=464119&
      redirect_uri=http%3A%2F%2Fexample.com%2Fcb%2F123 HTTP/1.1
> Host: connect.mail.ru

Здесь и далее, client_id и client_secret — значения, полученные при регистрации приложения на платформе.

После того, как пользователь выдаст права, происходит редирект на указанный redirect_uri:

< HTTP/1.1 302 Found
< Location: http://example.com/cb/123?code=DoRieb0y

Обратите внимание, если вы реализуете логин на сайте с помощью OAuth, то рекомендуется в redirect_uri добавлять уникальный для каждого пользователя идентификатор для предотвращения CSRF-атак (в примере это 123). При получении кода надо проверить, что этот идентификатор не изменился и соответствует текущему пользователю.

Используем полученный code для получения access_token, выполняя запрос с сервера:

> POST /oauth/token HTTP/1.1
> Host: connect.mail.ru
> Content-Type: application/x-www-form-urlencoded
> 
> grant_type=authorization_code&client_id=464119&client_secret=deadbeef&code=DoRieb0y&
  redirect_uri=http%3A%2F%2Fexample.com%2Fcb%2F123

< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
<    "access_token":"SlAV32hkKG",
<    "token_type":"bearer",
<    "expires_in":86400,
<    "refresh_token":"8xLOxBtZp8",
< }

Обратите внимание, что в последнем запросе используется client_secret, который в данном случае хранится на сервере приложения, и подтверждает, что запрос не подделан.

В результате последнего запроса получаем сам ключ доступа (access_token), время его «протухания&raquo (expires_in), тип ключа, определяющий как его надо использовать, (token_type) и refresh_token о котором будет подробнее сказано ниже. Дальше, полученные данные можно использовать для доступа к защищенным ресурсам, например, API Mail.Ru:

> GET /platform/api?oauth_token=SlAV32hkKG&client_id=464119&format=json&method=users.getInfo&
      sig=... HTTP/1.1
> Host: appsmail.ru

Описание в спецификации

Авторизация полностью клиентских приложений

Схема авторизации полностью клиентских платежей

  1. Открытие встроенного браузера со страницей авторизации
  2. У пользователя запрашивается подтверждение выдачи прав
  3. В случае согласия пользователя, браузер редиректится на страницу-заглушку во фрагменте (после #) URL которой добавляется access token
  4. Приложение перехватывает редирект и получает access token из адреса страницы

Этот вариант требует поднятия в приложении окна браузера, но не требует серверной части и дополнительного вызова сервер-сервер для обмена authorization code на access token.

Пример

Открываем браузер со страницей авторизации:

> GET /oauth/authorize?response_type=token&client_id=464119 HTTP/1.1
> Host: connect.mail.ru

После того, как пользователь выдаст права, происходит редирект на стандартную страницу-заглушку, для Mail.Ru это connect.mail.ru/oauth/success.html:

< HTTP/1.1 302 Found
< Location: http://connect.mail.ru/oauth/success.html#access_token=FJQbwq9&token_type=bearer&
            expires_in=86400&refresh_token=yaeFa0gu

Приложение должно перехватить последний редирект, получить из адреса acess_token и использовать его для обращения к защищенным ресурсам.

Описание в спецификации

Авторизация по логину и паролю

Авторизация по логину и паролю представляет простой POST-запрос, в результате которого возвращается access token. Такая схема не представляет из себя ничего нового, но вставлена в стандарт для общности и рекомендуется к применению только, когда другие варианты авторизации не доступны.

Пример

> POST /oauth/token HTTP/1.1
> Host: connect.mail.ru
> Content-Type: application/x-www-form-urlencoded
> 
> grant_type=password&client_id=31337&client_secret=deadbeef&username=api@corp.mail.ru&
  password=qwerty

< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
<    "access_token":"SlAV32hkKG",
<    "token_type":"bearer",
<    "expires_in":86400,
<    "refresh_token":"8xLOxBtZp8",
< }

Описание в спецификации

Восстановление предыдущей авторизации

Обычно, access token имеет ограниченный срок годности. Это может быть полезно, например, если он передается по открытым каналам. Чтобы не заставлять пользователя проходить авторизацию после истечения срока действия access token‘а, во всех перечисленных выше вариантах, в дополнение к access token‘у может возвращаться еще refresh token. По нему можно получить access token с помощью HTTP-запроса, аналогично авторизации по логину и паролю.

Пример

> POST /oauth/token HTTP/1.1
> Host: connect.mail.ru
> Content-Type: application/x-www-form-urlencoded
> 
> grant_type=refresh_token&client_id=31337&client_secret=deadbeef&refresh_token=8xLOxBtZp8

< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
<    "access_token":"Uu8oor1i",
<    "token_type":"bearer",
<    "expires_in":86400,
<    "refresh_token":"ohWo1ohr",
< }

Описание в спецификации

Минусы OAuth 2.0

Во всей этой красоте есть и ложка дегтя, куда без нее?

OAuth 2.0 — развивающийся стандарт. Это значит, что спецификация еще не устоялась и постоянно меняется, иногда довольно заметно. Так, что если вы решили поддержать стандарт прямо сейчас, приготовьтесь к тому, что его поддержку придется подпиливать по мере изменения спецификации. С другой стороны, это также значит, что вы можете поучаствовать в процессе написания стандарта и внести в него свои идеи.

Безопасность OAuth 2.0 во многом основана на SSL. Это сильно упрощает жизнь разработчикам, но требует дополнительных вычислительных ресурсов и администрирования. Это может быть существенным вопросом в высоко нагруженных проектах.

Заключение

OAuth — простой стандарт авторизации, основанный на базовых принципах интернета, что делает возможным применение авторизации практически на любой платформе. Стандарт имеет поддержку крупнейших площадок и очевидно, что его популярность будет только расти. Если вы задумались об API для вашего сервиса, то авторизация с использованием OAuth 2.0 — хороший выбор.

Со своей стороны, мы внедрили OAuth 2.0 в API Mail.Ru и, теперь, вы можете использовать возможности протокола для реализации любых клиентов и сервисов, интегрированных с Mail.Ru.

Ссылки

  • Текущая версия драфта стандарта OAuth 2.0
  • Официальный сайт OAuth
  • Рабочая группа по выработке стандарта (архивы)
  • Документация по реализации OAuth 2.0 в Mail.Ru

Дмитрий Битман — менеджер Платформы@Mail.Ru

Stay organized with collections

Save and categorize content based on your preferences.

The following document provides reference information about the status codes
and error messages that are used in the Cloud Storage JSON API. For
the page specific to the Cloud Storage XML API, see
HTTP status and error codes for XML.

Error Response Format

Cloud Storage uses the standard HTTP error reporting format for the
JSON API. Successful requests return HTTP status codes in the 2xx range. Failed
requests return status codes in the 4xx and 5xx ranges. Requests that require a
redirect returns status codes in the 3xx range. Error responses usually include
a JSON document in the response body, which contains information about the
error.

The following examples show some common errors. Note that the header
information in the responses is omitted.

The following is an example of an error response you receive if you try to
list the buckets for a project but do not provide an authorization header.

401 Unauthorized

{
"error": {
 "errors": [
  {
   "domain": "global",
   "reason": "required",
   "message": "Login Required",
   "locationType": "header",
   "location": "Authorization"
  }
 ],
 "code": 401,
 "message": "Login Required"
 }
}

403 Forbidden

This is an example of an error response you receive if you try to list the
buckets of a non-existent project or one in which you don’t have permission
to list buckets.

403 Forbidden

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "forbidden",
    "message": "Forbidden"
    }
  ],
  "code": 403,
  "message": "Forbidden"
 }
}

404 Not Found

The following is an example of an error response you receive if you try to
retrieve an object that does not exist.

404 Not Found

{
"error": {
 "errors": [
  {
   "domain": "global",
   "reason": "notFound",
   "message": "Not Found"
  }
 ],
 "code": 404,
 "message": "Not Found"
 }
}

409 Conflict

The following is an example of an error response you receive if you try to
create a bucket using the name of a bucket you already own.

409 Conflict

{
"error": {
 "errors": [
  {
   "domain": "global",
   "reason": "conflict",
   "message": "You already own this bucket. Please select another name."
  }
 ],
 "code": 409,
 "message": "You already own this bucket. Please select another name."
 }
}

The following table describes the elements that can appear in the response body
of an error. Fields should be used together to help determine the problem.
Also, the example values given below are meant for illustration and are not an
exhaustive list of all possible values.

Element Description
code An HTTP status code value, without the textual description.

Example values include: 400 (Bad Request), 401 (Unauthorized), and 404 (Not Found).

error A container for the error information.
errors A container for the error details.
errors.domain The scope of the error. Example values include: global and push.
errors.location The specific item within the locationType that caused the error. For example, if you specify an invalid value for a parameter, the location will be the name of the parameter.

Example values include: Authorization, project, and projection.

errors.locationType The location or part of the request that caused the error. Use with location to pinpoint the error. For example, if you specify an invalid value for a parameter, the locationType will be parameter and the location will be the name of the parameter.

Example values include header and parameter.

errors.message Description of the error.

Example values include Invalid argument, Login required, and Required parameter: project.

errors.reason Example values include invalid, invalidParameter, and required.
message Description of the error. Same as errors.message.

HTTP Status and Error Codes

This section provides a non-exhaustive list of HTTP status and error codes that
the Cloud Storage JSON API uses. The 1xx Informational and 2xx
Success codes are not discussed here. For more information, see Response Status
Codes in RFC 7231 §6, RFC 7232 §4,
RFC 7233 §4, RFC 7235 §3, and RFC 6585.

302—Found

Reason Description
found Resource temporarily located elsewhere according to the Location header.

303—See Other

Reason Description
mediaDownloadRedirect When requesting a download using alt=media URL parameter, the direct URL path to use is prefixed by /download. If this is omitted, the service will issue this redirect with the appropriate media download path in the Location header.

304—Not Modified

Reason Description
notModified The conditional request would have been successful, but the condition was false, so no body was sent.

307—Temporary Redirect

Reason Description
temporaryRedirect Resource temporarily located elsewhere according to the Location header. Among other reasons, this can occur when cookie-based authentication is being used, e.g., when using the Storage Browser, and it receives a request to download content.

308—Resume Incomplete

Description
Indicates an incomplete resumable upload and provides the range of bytes already received by Cloud Storage. Responses with this status do not contain a body.

400—Bad Request

[Domain.]Reason Description
badRequest The request cannot be completed based on your current Cloud Storage settings. For example, you cannot lock a retention policy if the requested bucket doesn’t have a retention policy, and you cannot set ACLs if the requested bucket has uniform bucket-level access enabled.
badRequestException The retention period on a locked bucket cannot be reduced.
cloudKmsBadKey Bad Cloud KMS key.
cloudKmsCannotChangeKeyName Cloud KMS key name cannot be changed.
cloudKmsDecryptionKeyNotFound Resource’s Cloud KMS decryption key not found.
cloudKmsDisabledKey Cloud KMS key is disabled, destroyed, or scheduled to be destroyed.
cloudKmsEncryptionKeyNotFound Cloud KMS encryption key not found.
cloudKmsKeyLocationNotAllowed Cloud KMS key location not allowed.
corsRequestWithXOrigin CORS request contains an XD3 X-Origin header.
customerEncryptionAlgorithmIsInvalid Missing an encryption algorithm, or the provided algorithm is not «AE256.»
customerEncryptionKeyFormatIsInvalid Missing an encryption key, or it is not Base64 encoded, or it does not meet the required length of the encryption algorithm.
customerEncryptionKeyIsIncorrect The provided encryption key is incorrect.
customerEncryptionKeySha256IsInvalid Missing a SHA256 hash of the encryption key, or it is not Base64 encoded, or it does not match the encryption key.
invalidAltValue The value for the alt URL parameter was not recognized.
invalidArgument The value for one of fields in the request body was invalid.
invalidParameter The value for one of the URL parameters was invalid. In addition to normal URL parameter validation, any URL parameters that have a corresponding value in provided JSON request bodies must match if they are both specified. If using JSONP, you will get this error if you provide an alt parameter that is not json.
notDownload Uploads or normal API request was sent to a /download/* path. Use the same path, but without the /download prefix.
notUpload Downloads or normal API request was sent to a /upload/* path. Use the same path, but without the /upload prefix.
parseError Could not parse the body of the request according to the provided Content-Type.
push.channelIdInvalid Channel id must match the following regular expression: [A-Za-z0-9\-_\+/=]+
push.channelIdNotUnique storage.objects.watchAll‘s id property must be unique across channels.
push.webhookUrlNoHostOrAddress storage.objects.watchAll‘s address property must contain a valid URL.
push.webhookUrlNotHttps storage.objects.watchAll‘s address property must be an HTTPS URL.
required A required URL parameter or required request body JSON property is missing.
resourceIsEncryptedWithCustomerEncryptionKey The resource is encrypted with a customer-supplied encryption key, but the request did not provide one.
resourceNotEncryptedWithCustomerEncryptionKey The resource is not encrypted with a customer-supplied encryption key, but the request provided one.
turnedDown A request was made to an API version that has been turned down. Clients will need to update to a supported version.
userProjectInvalid The user project specified in the request is invalid, either because it is a malformed project id or because it refers to a non-existent project.
userProjectMissing The requested bucket has Requester Pays enabled, the requester is not an owner of the bucket, and no user project was present in the request.
wrongUrlForUpload storage.objects.insert must be invoked as an upload rather than a metadata.

401—Unauthorized

[Domain.]Reason Description
AuthenticationRequiredRequesterPays Access to a Requester Pays bucket requires authentication.
authError This error indicates a problem with the authorization provided in the request to Cloud Storage. The following are some situations where that will occur:

  • The OAuth access token has expired and needs to be refreshed. This can be avoided by refreshing the access token early, but code can also catch this error, refresh the token and retry automatically.
  • Multiple non-matching authorizations were provided; choose one mode only.
  • The OAuth access token’s bound project does not match the project associated with the provided developer key.
  • The Authorization header was of an unrecognized format or uses an unsupported credential type.
lockedDomainExpired When downloading content from a cookie-authenticated site, e.g., using the Storage Browser, the response will redirect to a temporary domain. This error will occur if access to said domain occurs after the domain expires. Issue the original request again, and receive a new redirect.
required Access to a non-public method that requires authorization was made, but none was provided in the Authorization header or through other means.

403—Forbidden

[Domain.]Reason Description
accountDisabled The account associated with the project that owns the bucket or object has been disabled. Check the Google Cloud console to see if there is a problem with billing, and if not, contact account support.
countryBlocked The Cloud Storage JSON API is restricted by law from operating with certain countries.
forbidden According to access control policy, the current user does not have access to perform the requested action. This code applies even if the resource being acted on doesn’t exist.
insufficientPermissions According to access control policy, the current user does not have access to perform the requested action. This code applies even if the resource being acted on doesn’t exist.
objectUnderActiveHold Object replacement or deletion is not allowed due to an active hold on the object.
retentionPolicyNotMet Object replacement or deletion is not allowed until the object meets the retention period set by the retention policy on the bucket.
sslRequired Requests to this API require SSL.
stopChannelCallerNotOwner Calls to storage.channels.stop require that the caller own the channel.
UserProjectAccessDenied The requester is not authorized to use the project specified in the userProject portion of the request. The requester must have the serviceusage.services.use permission for the specified project.
UserProjectAccountProblem There is a problem with the project used in the request that prevents the operation from completing successfully. One issue could be billing. Check the billing page to see if you have a past due balance or if the credit card (or other payment mechanism) on your account is expired. For project creation, see the Projects page in the Google Cloud console. For other problems, see the Resources and Support page.

404—Not Found

Reason Description
notFound Either there is no API method associated with the URL path of the request, or the request refers to one or more resources that were not found.

405—Method Not Allowed

Reason Description
methodNotAllowed The HTTP verb is not supported by the URL endpoint used in the request. This can happen, for example, when using the wrong verb with the /upload or /download URLs.

408—Request Timeout

Reason Description
uploadBrokenConnection The request timed out. Please try again using truncated exponential backoff.

409—Conflict

Reason Description
conflict A request to change a resource, usually a storage.*.update or storage.*.patch method, failed to commit the change due to a conflicting concurrent change to the same resource. The request can be retried, though care should be taken to consider the new state of the resource to avoid blind replacement of another agent’s changes.

410—Gone

Description
You have attempted to use a resumable upload session or rewrite token that is no longer available. If the reported status code was not successful and you still wish to complete the upload or rewrite, you must start a new session.

411—Length Required

Description
You must provide the Content-Length HTTP header. This error has no response body.

412—Precondition Failed

Reason Description
conditionNotMet At least one of the pre-conditions you specified did not hold.
orgPolicyConstraintFailed Request violates an OrgPolicy constraint.

413—Payload Too Large

Reason Description
uploadTooLarge This error arises if you:

  • Attempt to upload an object larger than 5 TiB.
  • Attempt a Copy request that does not complete within 30 seconds and involves an object larger than 2.5 GiB. In this case, use the Rewrite method instead.

416—Requested Range Not Satisfiable

Reason Description
requestedRangeNotSatisfiable The requested Range cannot be satisfied.

429—Too Many Requests

[Domain.]Reason Description
usageLimits.rateLimitExceeded A Cloud Storage JSON API usage limit was exceeded. If your application tries to use more than its limit, additional requests will fail. Throttle your client’s requests, and/or use truncated exponential backoff.

499—Client Closed Request

Description
The resumable upload was cancelled at the client’s request prior to completion. This error has no response body.

500—Internal Server Error

Reason Description
backendError We encountered an internal error. Please try again using truncated exponential backoff.
internalError We encountered an internal error. Please try again using truncated exponential backoff.

502—Bad Gateway

This error is generated when there was difficulty reaching an internal service.
It is not formatted with a JSON document. Please try again using
truncated exponential backoff.

503—Service Unavailable

Reason Description
backendError We encountered an internal error. Please try again using truncated exponential backoff.

504—Gateway Timeout

This error is generated when there was difficulty reaching an internal service.
It is not formatted with a JSON document. Please try again using
truncated exponential backoff.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2023-02-07 UTC.


What is Error Response and Codes in OAuth 2.0?

  • The authorization server has error response which responds with HTTP 400 or 401 status codes.
  • If an error occurs during the authorization, two cases are given.

Case 1:

  • The client is not identified or recognized by the authorization server.

Case 2:

  • Despite the client being identified, some other error message is shown.
  • If that is the case, an error response is sent back to the client which is given as follows:

Error

  • Hence it is required and is given as a set of predefined error codes.

Error description

  • Error description is human readable error description given in the language specified by the Content-Language header
  • The error description parameter is used only to include ASCII characters, and it should be given as a sentence or two when describing the circumstance of the error.

Error Uri

  • This is given as a link to the human-readable web page which is given along with information about an error which can be helpful for problem solving.
  • The error uri is a link to the API documentation for information as per how to correct the specfic error which was encountered.
  • Error responses are returned with an HTTP 400 status code with error and error description parameters. The error parameters are given below as follows:
  • invalid_request is the request which is missing a parameter so the server can’t proceed with the request.
  • invalid_client is known for client authentication failed, such as the request contains an invalid client ID or secret.
  • invalid_grant is given the authorization code which is said to be invalid or expired. This is also can be given as the error we would return if the redirect URL given in the authorization grant does not match the URL which is provided in the access token request.
  • invalid_scope is done for access token requests that include a scope in which the error indicates an invalid scope value given in the request.
  • unauthorized_client is the client who is not authorized to use the requested grant type.
  • unsupported_grant_type is shown if a grant type is requested such that the authorization server does not recognize.
  • The entire error response is returned as a JSON string, which is given similar to the successful response.
  • Given below is an example of an error response.

Example:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
 
{
  "error": "invalid_request",
  "error_description": "Request was missing the 'redirect_uri' parameter.",
  "error_uri": "See the full API docs at
     <https://authorization-server.com/docs/access_token>"
}
click below button to copy the code. By — oauth tutorial — oauth2 tutorial — team
  • Description of error codes and equivalent HTTP status codes are given below in form of tables:

400 Errors

  • The table which is given below shows us the description of 400 errors.
Sr.No. Error & Description
1 unsupported_over_http

OAuth 2.0 only supports the calls over https.

2 version_rejected

If an unsupported version of OAuth is supplied.

3 parameter_absent

If a required parameter is missing from the request.

4 parameter_rejected

When a given parameter is too long.

5 invalid_client

When an invalid client ID is given.

6 invalid_request

When an invalid request parameter is given.

7 unsupported_response_type

When a response type provided does not match that particular request.

8 unsupported_grant_type

When a grant type is provided that does not match a particular request.

9 invalid_param

When an invalid request parameter is provided.

10 unauthorized_client

When the client is not given the permission to perform some action.

11 access_denied

When the resource owner refuses the request for authorization.

12 server_error

This error displays an unexpected error.

401 Errors

  • The table which is given below shows us the description of 401 errors.
Sr.No. Error & Description
1 token_expired

When the provided token expires.

2 invalid_token

When the provided token is invalid.

3 invalid_callback

When the provided URI with the request does not match the consumer key.

4 invalid_client_secret

When the provided client server is invalid.

5 invalid_grant

When the provided token has either expired or is invalid.


Понравилась статья? Поделить с друзьями:
  • An error occurred while trying to copy a file out of memory
  • An error occurred while trying to connect to the java server
  • An error occurred while trying to connect to server please try again later перевод
  • An error occurred while trying to compute required packages
  • An error occurred while the page was being generated please try again later