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.
- Error description
- Short error description in the response
- 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 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, такова:
- получение авторизации
- обращение к защищенным ресурсам
Результатом авторизации является access token — некий ключ (обычно просто набор символов), предъявление которого является пропуском к защищенным ресурсам. Обращение к ним в самом простом случае происходит по HTTPS с указанием в заголовках или в качестве одного из параметров полученного access token‘а.
В протоколе описано несколько вариантов авторизации, подходящих для различных ситуаций:
- авторизация для приложений, имеющих серверную часть (чаще всего, это сайты и веб-приложения)
- авторизация для полностью клиентских приложений (мобильные и desktop-приложения)
- авторизация по логину и паролю
- восстановление предыдущей авторизации
Авторизация для приложений, имеющих серверную часть
- Редирект на страницу авторизации
- На странице авторизации у пользователя запрашивается подтверждение выдачи прав
- В случае согласия пользователя, браузер редиректится на URL, указанный при открытии страницы авторизации, с добавлением в GET-параметры специального ключа — authorization code
- Сервер приложения выполняет 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), время его «протухания» (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
Описание в спецификации
Авторизация полностью клиентских приложений
- Открытие встроенного браузера со страницей авторизации
- У пользователя запрашивается подтверждение выдачи прав
- В случае согласия пользователя, браузер редиректится на страницу-заглушку во фрагменте (после #) URL которой добавляется access token
- Приложение перехватывает редирект и получает 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: |
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: |
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 |
errors.message |
Description of the error.
Example values include |
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:
|
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:
|
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. |