diff --git a/docs/spec/auth/index.md b/docs/spec/auth/index.md
index 2d1740e8..e0e8aaf6 100644
--- a/docs/spec/auth/index.md
+++ b/docs/spec/auth/index.md
@@ -8,5 +8,6 @@ keywords = ["registry, on-prem, images, tags, repository, distribution, authenti
# Docker Registry v2 authentication
-See the [Token Authentication Specification](token.md) and
-[Token Authentication Implementation](jwt.md) for more information.
+See the [Token Authentication Specification](token.md),
+[Token Authentication Implementation](jwt.md), and
+[OAuth2 Token Authentication](oauth.md) for more information.
diff --git a/docs/spec/auth/oauth.md b/docs/spec/auth/oauth.md
new file mode 100644
index 00000000..56d7d943
--- /dev/null
+++ b/docs/spec/auth/oauth.md
@@ -0,0 +1,122 @@
+
+
+# Docker Registry v2 authentication using OAuth2
+
+This document describes support for the OAuth2 protocol within the authorization
+server. [RFC6749](https://tools.ietf.org/html/rfc6749) should be used as a
+reference for the protocol and HTTP endpoints described here.
+
+## Refresh token format
+
+The format of the refresh token is completely opaque to the client and should be
+determined by the authorization server. The authorization should ensure the
+token is sufficiently long and is responsible for storing any information about
+long-lived tokens which may be needed for revoking. Any information stored
+inside the token will not be extracted and presented by clients.
+
+## Getting a token
+
+POST /token
+
+#### Headers
+Authorization headers
+Content-Type: application/x-www-form-urlencoded
+
+#### Post parameters
+
+
+ -
+
grant_type
+
+ -
+ (REQUIRED) Type of grant used to get token. When getting a refresh token
+ using credentials this type should be set to "password" and have the
+ accompanying basic auth header. Type "authorization_code" is reserved
+ for future use for authenticating to an authorization server without
+ having to send credentials directly from the client. When requesting an
+ access token with a refresh token this should be set to "refresh_token".
+
+ -
+
service
+
+ -
+ (REQUIRED) The name of the service which hosts the resource to get
+ access for. Refresh tokens will only be good for getting tokens for
+ this service.
+
+ -
+
client
+
+ -
+ (REQUIRED) The name of the client which is getting accessed. Intended to be human
+ readable for key auditing.
+
+ -
+
access_type
+
+ -
+ (OPTIONAL) Access which is being requested. If "offline" is provided then a refresh
+ token will be returned. Otherwise only a short lived access token will
+ be returned. If the grant type is "refresh_token" this will only return
+ the same refresh token and not a new one.
+
+ -
+
scope
+
+ -
+ (OPTIONAL) The resource in question, formatted as one of the space-delimited
+ entries from the
scope
parameters from the WWW-Authenticate
header
+ shown above. This query parameter should be specified multiple times if
+ there is more than one scope
entry from the WWW-Authenticate
+ header. The above example would be specified as:
+ scope=repository:samalba/my-app:push
. When requesting a refresh
+ token the scopes may be empty since the refresh token will not be limited by
+ this scope, only the provided short lived access token.
+
+ -
+
refresh_token
+
+ -
+ (OPTIONAL) The refresh token to use for authentication when grant type "refresh_token" is used.
+
+
+
+#### Example getting refresh token
+
+```
+POST /token HTTP/1.1
+Host: auth.docker.io
+Authorization: ...
+Content-Type: application/x-www-form-urlencoded
+
+grant_type=password&service=hub.docker.io&client=dockerengine&access_type=offline
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{"refresh_token":"xT2s5VFNrbzZTMVExUmpwWVRsSklPbFJMTmtnNlMxUkxOanBCUVV0VU1Ga3d","access_token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDTHpDQ0FkU2dBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakJHTVVRd1FnWURWUVFERXp0Uk5Gb3pPa2RYTjBrNldGUlFSRHBJVFRSUk9rOVVWRmc2TmtGRlF6cFNUVE5ET2tGU01rTTZUMFkzTnpwQ1ZrVkJPa2xHUlVrNlExazFTekFlRncweE5UQTJNalV4T1RVMU5EWmFGdzB4TmpBMk1qUXhPVFUxTkRaYU1FWXhSREJDQmdOVkJBTVRPMGhHU1UwNldGZFZWam8yUVZkSU9sWlpUVEk2TTFnMVREcFNWREkxT2s5VFNrbzZTMVExUmpwWVRsSklPbFJMTmtnNlMxUkxOanBCUVV0VU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXl2UzIvdEI3T3JlMkVxcGRDeFdtS1NqV1N2VmJ2TWUrWGVFTUNVMDByQjI0akNiUVhreFdmOSs0MUxQMlZNQ29BK0RMRkIwVjBGZGdwajlOWU5rL2pxT0JzakNCcnpBT0JnTlZIUThCQWY4RUJBTUNBSUF3RHdZRFZSMGxCQWd3QmdZRVZSMGxBREJFQmdOVkhRNEVQUVE3U0VaSlRUcFlWMVZXT2paQlYwZzZWbGxOTWpveldEVk1PbEpVTWpVNlQxTktTanBMVkRWR09saE9Va2c2VkVzMlNEcExWRXMyT2tGQlMxUXdSZ1lEVlIwakJEOHdQWUE3VVRSYU16cEhWemRKT2xoVVVFUTZTRTAwVVRwUFZGUllPalpCUlVNNlVrMHpRenBCVWpKRE9rOUdOemM2UWxaRlFUcEpSa1ZKT2tOWk5Vc3dDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTXZiT2h4cHhrTktqSDRhMFBNS0lFdXRmTjZtRDFvMWs4ZEJOVGxuWVFudkFpRUF0YVJGSGJSR2o4ZlVSSzZ4UVJHRURvQm1ZZ3dZelR3Z3BMaGJBZzNOUmFvPSJdfQ.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImRtY2dvd2FuL2hlbGxvLXdvcmxkIiwiYWN0aW9ucyI6WyJwdWxsIl19XSwiYXVkIjoicmVnaXN0cnkuZG9ja2VyLmlvIiwiZXhwIjoxNDU0NDM4Njc1LCJpYXQiOjE0NTQ0MzgzNzUsImlzcyI6ImF1dGguZG9ja2VyLmlvIiwianRpIjoiZXFrVmVsWWJtbW5KSDctNW53SEkiLCJuYmYiOjE0NTQ0MzgzNzUsInN1YiI6ImRtY2dvd2FuIn0"}
+````
+
+#### Example refreshing an Access Token
+
+````
+POST /token HTTP/1.1
+Host: auth.docker.io
+Content-Type: application/x-www-form-urlencoded
+
+grant_type=refresh_token&refresh_token=kas9Da81Dfa8&service=registry-1.docker.io&client=dockerengine&scope=repository:samalba/my-app:pull,push
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{"access_token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDTHpDQ0FkU2dBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakJHTVVRd1FnWURWUVFERXp0Uk5Gb3pPa2RYTjBrNldGUlFSRHBJVFRSUk9rOVVWRmc2TmtGRlF6cFNUVE5ET2tGU01rTTZUMFkzTnpwQ1ZrVkJPa2xHUlVrNlExazFTekFlRncweE5UQTJNalV4T1RVMU5EWmFGdzB4TmpBMk1qUXhPVFUxTkRaYU1FWXhSREJDQmdOVkJBTVRPMGhHU1UwNldGZFZWam8yUVZkSU9sWlpUVEk2TTFnMVREcFNWREkxT2s5VFNrbzZTMVExUmpwWVRsSklPbFJMTmtnNlMxUkxOanBCUVV0VU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXl2UzIvdEI3T3JlMkVxcGRDeFdtS1NqV1N2VmJ2TWUrWGVFTUNVMDByQjI0akNiUVhreFdmOSs0MUxQMlZNQ29BK0RMRkIwVjBGZGdwajlOWU5rL2pxT0JzakNCcnpBT0JnTlZIUThCQWY4RUJBTUNBSUF3RHdZRFZSMGxCQWd3QmdZRVZSMGxBREJFQmdOVkhRNEVQUVE3U0VaSlRUcFlWMVZXT2paQlYwZzZWbGxOTWpveldEVk1PbEpVTWpVNlQxTktTanBMVkRWR09saE9Va2c2VkVzMlNEcExWRXMyT2tGQlMxUXdSZ1lEVlIwakJEOHdQWUE3VVRSYU16cEhWemRKT2xoVVVFUTZTRTAwVVRwUFZGUllPalpCUlVNNlVrMHpRenBCVWpKRE9rOUdOemM2UWxaRlFUcEpSa1ZKT2tOWk5Vc3dDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTXZiT2h4cHhrTktqSDRhMFBNS0lFdXRmTjZtRDFvMWs4ZEJOVGxuWVFudkFpRUF0YVJGSGJSR2o4ZlVSSzZ4UVJHRURvQm1ZZ3dZelR3Z3BMaGJBZzNOUmFvPSJdfQ.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImRtY2dvd2FuL2hlbGxvLXdvcmxkIiwiYWN0aW9ucyI6WyJwdWxsIl19XSwiYXVkIjoicmVnaXN0cnkuZG9ja2VyLmlvIiwiZXhwIjoxNDU0NDM4Njc1LCJpYXQiOjE0NTQ0MzgzNzUsImlzcyI6ImF1dGguZG9ja2VyLmlvIiwianRpIjoiZXFrVmVsWWJtbW5KSDctNW53SEkiLCJuYmYiOjE0NTQ0MzgzNzUsInN1YiI6ImRtY2dvd2FuIn0"}
+````
+
diff --git a/docs/spec/auth/token.md b/docs/spec/auth/token.md
index 61e893c0..a953ede2 100644
--- a/docs/spec/auth/token.md
+++ b/docs/spec/auth/token.md
@@ -91,6 +91,8 @@ challenge, the client will need to make a `GET` request to the URL
## Requesting a Token
+Defines getting a bearer and refresh token using the token endpoint.
+
#### Query Parameters
@@ -100,6 +102,15 @@ challenge, the client will need to make a `GET` request to the URL
-
The name of the service which hosts the resource.
+ -
+
offline_token
+
+ -
+ Whether to return a refresh token along with the bearer token. A refresh
+ token is capable of getting additional bearer tokens for the same
+ subject with different scopes. The refresh token does not have an
+ expiration and should be considered completely opaque to the client.
+
-
scope
@@ -109,7 +120,9 @@ challenge, the client will need to make a `GET` request to the URL
shown above. This query parameter should be specified multiple times if
there is more than one scope
entry from the WWW-Authenticate
header. The above example would be specified as:
- scope=repository:samalba/my-app:push
.
+ scope=repository:samalba/my-app:push
. The scope field may
+ be empty to request a refresh token without providing any resource
+ permissions to the returned bearer token.
@@ -150,6 +163,16 @@ challenge, the client will need to make a `GET` request to the URL
standard time at which a given token was issued. If issued_at
is omitted, the
expiration is from when the token exchange completed.
+
+ refresh_token
+
+
+ (Optional) Token which can be used to get additional access tokens for
+ the same subject with different scopes. This token should be kept secure
+ by the client and only sent to the authorization server which issues
+ bearer tokens. This field will only be set when `offline_token=true` is
+ provided in the request.
+
#### Example
@@ -161,11 +184,12 @@ https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba
```
The token server should first attempt to authenticate the client using any
-authentication credentials provided with the request. As of Docker 1.8, the
-registry client in the Docker Engine only supports Basic Authentication to
-these token servers. If an attempt to authenticate to the token server fails,
-the token server should return a `401 Unauthorized` response indicating that
-the provided credentials are invalid.
+authentication credentials provided with the request. From Docker 1.11 the
+Docker engine supports both Basic Authentication and [OAuth2](oauth.md) for
+getting tokens. Docker 1.10 and before, the registry client in the Docker Engine
+only supports Basic Authentication. If an attempt to authenticate to the token
+server fails, the token server should return a `401 Unauthorized` response
+indicating that the provided credentials are invalid.
Whether the token server requires authentication is up to the policy of that
access control provider. Some requests may require authentication to determine