OAuth 2.0 官网

OAuth 2.0

 

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.

 

OAuth 2.0

Mobile and Other Devices

Token and Token Management

Discovery and Registration

Experimental and Draft Specs

The specs below are either experimental or in draft status and are still active working group items. They will likely change before they are finalized as RFCs or BCPs.

Related Specs and Extensions

Community Resources

Protocols Built on OAuth 2.0

Code and Services

OAuth 2.1

Legacy

 

RFC 6750: OAuth 2.0 Bearer Token Usage

tools.ietf.org/html/rfc6750

Bearer Tokens are the predominant type of access token used with OAuth 2.0.

A Bearer Token is an opaque string, not intended to have any meaning to clients using it. Some servers will issue tokens that are a short string of hexadecimal characters, while others may use structured tokens such as JSON Web Tokens.

More resources

 

     Access Tokens

12

 

Access tokens are the thing that applications use to make API requests on behalf of a user. The access token represents the authorization of a specific application to access specific parts of a user’s data.

Access tokens must be kept confidential in transit and in storage. The only parties that should ever see the access token are the application itself, the authorization server, and resource server. The application should ensure the storage of the access token is not accessible to other applications on the same device. The access token can only be used over an https connection, since passing it over a non-encrypted channel would make it trivial for third parties to intercept.

The token endpoint is where apps make a request to get an access token for a user. This section describes how to verify token requests and how to return the appropriate response and errors.

 

Successful Response

If the request for an access token is valid, the authorization server needs to generate an access token (and optional refresh token) and return these to the client, typically along with some additional properties about the authorization.

The response with an access token should contain the following properties:

  • access_token (required) The access token string as issued by the authorization server.
  • token_type (required) The type of token this is, typically just the string “bearer”.
  • expires_in (recommended) If the access token expires, the server should reply with the duration of time the access token is granted for.
  • refresh_token (optional) If the access token will expire, then it is useful to return a refresh token which applications can use to obtain another access token. However, tokens issued with the implicit grant cannot be issued a refresh token.
  • scope (optional) If the scope the user granted is identical to the scope the app requested, this parameter is optional. If the granted scope is different from the requested scope, such as if the user modified the scope, then this parameter is required.

When responding with an access token, the server must also include the additional Cache-Control: no-store and Pragma: no-cache HTTP headers to ensure clients do not cache this request.

For example, a successful token response may look like the following:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
 
{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}

Access Tokens

The format for OAuth 2.0 Bearer tokens is actually described in a separate spec, RFC 6750. There is no defined structure for the token required by the spec, so you can generate a string and implement tokens however you want. The valid characters in a bearer token are alphanumeric, and the following punctuation characters:

-._~+/

Typically a service will either generate random strings and store them in a database along with the associated user and scope information, or will use Self-Encoded tokens where the token string itself contains all the necessary info.

Unsuccessful Response

If the access token request is invalid, such as the redirect URL didn’t match the one used during authorization, then the server needs to return an error response.

Error responses are returned with an HTTP 400 status code (unless specified otherwise), with error and error_description parameters. The error parameter will always be one of the values listed below.

  • invalid_request – The request is missing a parameter so the server can’t proceed with the request. This may also be returned if the request includes an unsupported parameter or repeats a parameter.
  • invalid_client – Client authentication failed, such as if the request contains an invalid client ID or secret. Send an HTTP 401 response in this case.
  • invalid_grant – The authorization code (or user’s password for the password grant type) is invalid or expired. This is also the error you would return if the redirect URL given in the authorization grant does not match the URL provided in this access token request.
  • invalid_scope – For access token requests that include a scope (password or client_credentials grants), this error indicates an invalid scope value in the request.
  • unauthorized_client – This client is not authorized to use the requested grant type. For example, if you restrict which applications can use the Implicit grant, you would return this error for the other apps.
  • unsupported_grant_type – If a grant type is requested that the authorization server doesn’t recognize, use this code. Note that unknown grant types also use this specific error code rather than using the invalid_request above.

There are two optional parameters when returning an error response, error_description and error_uri. These are meant to give developers more information about the error, not intended to be shown to end users. However, keep in mind that many developers will pass this error text straight on to end users no matter how much you warn them, so it is a good idea to make sure it is at least somewhat helpful to end users as well.

The error_description parameter can only include ASCII characters, and should be a sentence or two at most describing the circumstance of the error. The error_uri is a great place to link to your API documentation for information about how to correct the specific error that was encountered.

The entire error response is returned as a JSON string, similar to the successful response. Below is an example of an error response.

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"
}

     Client Credentials

12.3

 

The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.

Request Parameters

grant_type (required)

The grant_type parameter must be set to client_credentials.

scope (optional)

Your service can support different scopes for the client credentials grant. In practice, not many services actually support this.

Client Authentication (required)

The client needs to authenticate themselves for this request. Typically the service will allow either additional request parameters client_id and client_secret, or accept the client ID and secret in the HTTP Basic auth header.

Example

The following is an example authorization code grant the service would receive.

POST /token HTTP/1.1
Host: authorization-server.com
 
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

See Access Token Response for details on the parameters to return when generating an access token or responding to errors.

 

     Access Token Lifetime

12.6

 

When your service issues access tokens, you’ll need to make some decisions as to how long you want the tokens to last. Unfortunately there is no blanket solution for every service. There are various tradeoffs that come with the different options, so you should choose the option (or combination of options) that best suit your application’s need.

Short-lived access tokens and long-lived refresh tokens

A common method of granting tokens is to use a combination of access tokens and refresh tokens for maximum security and flexibility. The OAuth 2.0 spec recommends this option, and several of the larger implementations have gone with this approach.

Typically services using this method will issue access tokens that last anywhere from several hours to a couple weeks. When the service issues the access token, it also generates a refresh token that never expires and returns that in the response as well. (Note that refresh tokens can’t be issued using the Implicit grant.)

When the access token expires, the application can use the refresh token to obtain a new access token. It can do this behind the scenes, and without the user’s involvement, so that it’s a seamless process to the user.

The main benefit of this approach is that the service can use self-encoded access tokens which can be verified without a database lookup. However, this means there is no way to expire those tokens directly, so instead, the tokens are issued with a short expiration time so that the application is forced to continually refresh them, giving the service a chance to revoke an application’s access if needed.

From the third-party developer’s perspective, it is often frustrating to have to deal with refresh tokens. Developers strongly prefer access tokens that don’t expire, since it’s much less code to deal with. In order to help mitigate these concerns, services will often build the token refreshing logic into their SDK, so that the process is transparent to developers.

In summary, use short-lived access tokens and long-lived refresh tokens when:

  • you want to use self-encoded access tokens
  • you want to limit the risk of leaked access tokens
  • you will be providing SDKs that can handle the refresh logic transparently to developers

Short-lived access tokens and no refresh tokens

If you want to ensure users are aware of applications that are accessing their account, the service can issue relatively short-lived access tokens without refresh tokens. The access tokens may last anywhere from the current application session to a couple weeks. When the access token expires, the application will be forced to make the user sign in again, so that you as the service know the user is continually involved in re-authorizing the application.

Typically this option is used by services where there is a high risk of damage if a third-party application were to accidentally or maliciously leak access tokens. By requiring that users are constantly re-authorizing the application, the service can ensure that potential damage is limited if an attacker were to steal access tokens from the service.

By not issuing refresh tokens, this makes it impossible to applications to use the access token on an ongoing basis without the user in front of the screen. Applications that need access in order to continually sync data will be unable to do so under this method.

From the user’s perspective, this is the option most likely to frustrate people, since it will look like the user has to continually re-authorize the application.

In summary, use short-lived access tokens with no refresh tokens when:

  • you want to the most protection against the risk of leaked access tokens
  • you want to force users to be aware of third-party access they are granting
  • you don’t want third-party apps to have offline access to users’ data

Non-expiring access tokens

Non-expiring access tokens are the easiest method for developers. If you choose this option, it is important to consider the trade-offs you are making.

It isn’t practical to use self-encoded tokens if you want to be able to revoke them arbitrarily. As such, you’ll need to store these tokens in some sort of database, so they can be deleted or marked as invalid as needed.

Note that even if the service intends on issuing non-expiring access tokens for normal use, you’ll still need to provide a mechanism to expire them under exceptional circumstances, such as if the user explicitly wants to revoke an application’s access, or if a user account is deleted.

Non-expiring access tokens are much easier for developers testing their own applications. You can even pre-generate one or more non-expiring access tokens for developers and show it to them on the application details screen. This way they can immediately start making API requests with the token, and not worry about setting up an OAuth flow in order to start testing your API.

In summary, use non-expiring access tokens when:

  • you have a mechanism to revoke access tokens arbitrarily
  • you don’t have a huge risk if tokens are leaked
  • you want to provide an easy authentication mechanism to your developers
  • you want third-party applications to have offline access to users’ data

 

     Refreshing Access Tokens

12.7

 

This section describes how to allow your developers to use refresh tokens to obtain new access tokens. If your service issues refresh tokens along with the access token, then you’ll need to implement the Refresh grant type described here.

Request Parameters

The access token request will contain the following parameters.

grant_type (required)

The grant_type parameter must be set to “refresh_token”.

refresh_token (required)

The refresh token previously issued to the client.

scope (optional)

The requested scope must not include additional scopes that were not issued in the original access token. Typically this will not be included in the request, and if omitted, the service should issue an access token with the same scope as was previously issued.

Client Authentication (required if the client was issued a secret)

Typically, refresh tokens are only used with confidential clients. However, since it is possible to use the authorization code flow without a client secret, the refresh grant may also be used by clients that don’t have a secret. If the client was issued a secret, then the client must authenticate this request. Typically the service will allow either additional request parameters client_id and client_secret, or accept the client ID and secret in the HTTP Basic auth header. If the client does not have a secret, then no client authentication will be present in this request.

Verifying the refresh token grant

After checking for all required parameters, and authenticating the client if the client was issued a secret, the authorization server can continue verifying the other parts of the request.

The server then checks whether the refresh token is valid, and has not expired. If the refresh token was issued to a confidential client, the service must ensure the refresh token in the request was issued to the authenticated client.

If everything checks out, the service can generate an access token and respond. The server may issue a new refresh token in the response, but if the response does not include a new refresh token, the client assumes the existing refresh token will still be valid.

Example

The following is an example refresh grant the service would receive.

POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
grant_type=refresh_token
&refresh_token=xxxxxxxxxxx
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

Response

The response to the refresh token grant is the same as when issuing an access token. You can optionally issue a new refresh token in the response, or if you don’t include a new refresh token, the client assumes the current refresh token will continue to be valid.

 

     Self-Encoded Access Tokens

12.5

 

Self-encoded tokens provide a way to avoid storing tokens in a database by encoding all of the necessary information in the token string itself. The main benefit of this is that API servers are able to verify access tokens without doing a database lookup on every API request, making the API much more easily scalable.

The benefit of OAuth 2.0 Bearer Tokens is that applications don’t need to be aware of how you’ve decided to implement access tokens in your service. This means it’s possible to change your implementation later without affecting clients.

If you already have a distributed database system that is horizontally scalable, then you may not gain any benefits by using self-encoded tokens. In fact, using self-encoded tokens if you’ve already solved the distributed database problem will only introduce new issues, as invalidating self-encoded tokens becomes an additional hurdle.

There are many ways to self-encode tokens. The actual method you choose is only important to your implementation, since the token information is not exposed to external developers.

One way to create self-encoded tokens is to create a JSON-serialized representation of all the data you want to include in the token, and sign the resulting string with a key known only to your server.

A common technique for this is using the JSON Web Signature (JWS) standard to handle encoding, decoding and verification of tokens. The JSON Web Token (JWT) specification defines some terms you can use in the JWS, as well as defines some timestamp terms to determine whether a token is valid. We’ll use a JWT library in this example, since it provides built-in handling of expiration.

Encoding

The code below is written in PHP and uses the Firebase PHP-JWT library to encode and verify tokens. You’ll need to include that library in order to run the sample code

<?php
use \Firebase\JWT\JWT;
 
# Define the secret key used to create and verify the signature
$jwt_key = 'secret';
 
# Set the user ID of the user this token is for
$user_id = 1000;
 
# Set the client ID of the app that is generating this token
$client_id = 'https://example-app.com';
 
# Provide the list of scopes this token is valid for
$scope = 'read write';
 
$token_data = array(
 
  # Subject (The user ID)
  'sub' => $user_id,
 
  # Issuer (the token endpoint)
  'iss' => 'https://' . $_SERVER['PHP_SELF'],
 
  # Client ID (this is a non-standard claim)
  'cid' => $client_id,
 
  # Issued At
  'iat' => time(),
 
  # Expires At
  'exp' => time()+7200, // Valid for 2 hours
 
  # The list of OAuth scopes this token includes
  'scope' => $scope
);
$token_string = JWT::encode($token_data, $jwt_key);

This will result in a string such as:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEwMDAsI
mlzcyI6Imh0dHBzOi8vYXV0aG9yaXphdGlvbi1zZXJ2ZXIuY29tIiw
iY2lkIjoiaHR0cHM6Ly9leGFtcGxlLWFwcC5jb20iLCJpYXQiOjE0N
zAwMDI3MDMsImV4cCI6MTUyOTE3NDg1MSwic2NvcGUiOiJyZWFkIHd
yaXRlIn0.QiIrnmaC4VrbAYAsu0YPeuJ992p20fSxrXWPLw-gkFA

This token is made up of three components, separated by periods. The first part describes the signature method used. The second part contains the token data. The third part is the signature.

For example, this token’s first component is this JSON object:

{
   "typ":"JWT",
   "alg":"HS256”
 }

 

The second component contains the actual data the API endpoint needs in order to process the request, such as user identification and scope access.

{
  "sub": 1000,
  "iat": 1470002703,
  "exp": 1470009903,
  "scope": "read write"
}

 

Base64-encoding the first two components results in these following two strings:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
eyJzdWIiOjEwMDAsImlzcyI6Imh0dHBzOi8vYXV0aG9yaXphdGlvbi1z
ZXJ2ZXIuY29tIiwiY2lkIjoiaHR0cHM6Ly9leGFtcGxlLWFwcC5jb20i
LCJpYXQiOjE0NzAwMDI3MDMsImV4cCI6MTUyOTE3NDg1MSwic2NvcGUi
OiJyZWFkIHdyaXRlIn0

We then calculate a hash of the two strings along with a secret, resulting in another string:

QiIrnmaC4VrbAYAsu0YPeuJ992p20fSxrXWPLw-gkFA

 

Finally, concatenate all three strings together separated by periods.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEwMDAsI
mlzcyI6Imh0dHBzOi8vYXV0aG9yaXphdGlvbi1zZXJ2ZXIuY29tIiw
iY2lkIjoiaHR0cHM6Ly9leGFtcGxlLWFwcC5jb20iLCJpYXQiOjE0N
zAwMDI3MDMsImV4cCI6MTUyOTE3NDg1MSwic2NvcGUiOiJyZWFkIHd
yaXRlIn0.QiIrnmaC4VrbAYAsu0YPeuJ992p20fSxrXWPLw-gkFA

Decoding

Verifying the access token can be done by using the same JWT library. The library will decode and verify the signature at the same time, and throws an exception if the signature was invalid, or if the expiration date of the token has already passed.

Note: Anyone can read the token information by base64-decoding the middle section of the token string. For this reason, it’s important that you do not store private information or information you do not want a user or developer to see in the token. If you want to hide the token information, you can use the JSON Web Encryption spec to encrypt the data in the token.
try {
  # Note: You must provide the list of supported algorithms in order to prevent
  # an attacker from bypassing the signature verification. See:
  # https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
  $token = JWT::decode($token_string, $jwt_key, ['HS256']);
  $error = false;
} catch(\Firebase\JWT\ExpiredException $e) {
  $token = false;
  $error = 'expired';
  $error_description = 'The token has expired';
} catch(\Firebase\JWT\SignatureInvalidException $e) {
  $token = false;
  $error = 'invalid';
  $error_description = 'The token provided was malformed';
} catch(Exception $e) {
  $token = false;
  $error = 'unauthorized';
  $error_description = $e->getMessage();
}
 
if($error) {
  header('HTTP/1.1 401 Unauthorized');
  echo json_encode(array(
    'error'=>$error,
    'error_description'=>$error_description
  ));
  die();
} else {
  // Now $token has all the data that we encoded in it originally
  print_r($token);
}

At this point, the service has all the information it needs such as the user ID, scope, etc, available to it, and didn’t have to do a database lookup. Next it can check to make sure the access token hasn’t expired, can verify the scope is sufficient to perform the requested operation, and can then process the request.

Invalidating

Because the token can be verified without doing a database lookup, there is no way to invalidate a token until it expires. You’ll need to take additional steps to invalidate tokens that are self-encoded. See Refreshing Access Tokens for more information.

 

 

Refresh Tokens: When to Use Them and How They Interact with JWTs

Access tokens carry the necessary information to access a resource directly. In other words, when a client passes an access token to a server managing a resource, that server can use the information contained in the token to decide whether the client is authorized or not. Access tokens usually have an expiration date and are short-lived.

 

 

  • Refresh tokens carry the information necessary to get a new access token. In other words, whenever an access token is required to access a specific resource, a client may use a refresh token to get a new access token issued by the authentication server. Common use cases include getting new access tokens after old ones have expired, or getting access to a new resource for the first time. Refresh tokens can also expire but are rather long-lived. Refresh tokens are usually subject to strict storage requirements to ensure they are not leaked. They can also be blacklisted by the authorization server.

Refresh Token

RFC 7662: Token Introspection

tools.ietf.org/html/rfc7662

The Token Introspection extension defines a mechanism for resource servers to obtain information about access tokens. With this spec, resource servers can check the validity of access tokens, and find out other information such as which user and which scopes are associated with the token.

More resources

 

 

 

 

posted @ 2021-05-14 15:25  PanPan003  阅读(356)  评论(0编辑  收藏  举报