github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/docs/spec/auth/jwt.md (about) 1 <!--[metadata]> 2 +++ 3 title = "Token Authentication Implementation" 4 description = "Describe the reference implementation of the Docker Registry v2 authentication schema" 5 keywords = ["registry, on-prem, images, tags, repository, distribution, JWT authentication, advanced"] 6 [menu.main] 7 parent="smn_registry_ref" 8 +++ 9 <![end-metadata]--> 10 11 # Docker Registry v2 Bearer token specification 12 13 This specification covers the `docker/distribution` implementation of the 14 v2 Registry's authentication schema. Specifically, it describes the JSON 15 Web Token schema that `docker/distribution` has adopted to implement the 16 client-opaque Bearer token issued by an authentication service and 17 understood by the registry. 18 19 This document borrows heavily from the [JSON Web Token Draft Spec](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32) 20 21 ## Getting a Bearer Token 22 23 For this example, the client makes an HTTP GET request to the following URL: 24 25 ``` 26 https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push 27 ``` 28 29 The token server should first attempt to authenticate the client using any 30 authentication credentials provided with the request. As of Docker 1.8, the 31 registry client in the Docker Engine only supports Basic Authentication to 32 these token servers. If an attempt to authenticate to the token server fails, 33 the token server should return a `401 Unauthorized` response indicating that 34 the provided credentials are invalid. 35 36 Whether the token server requires authentication is up to the policy of that 37 access control provider. Some requests may require authentication to determine 38 access (such as pushing or pulling a private repository) while others may not 39 (such as pulling from a public repository). 40 41 After authenticating the client (which may simply be an anonymous client if 42 no attempt was made to authenticate), the token server must next query its 43 access control list to determine whether the client has the requested scope. In 44 this example request, if I have authenticated as user `jlhawn`, the token 45 server will determine what access I have to the repository `samalba/my-app` 46 hosted by the entity `registry.docker.io`. 47 48 Once the token server has determined what access the client has to the 49 resources requested in the `scope` parameter, it will take the intersection of 50 the set of requested actions on each resource and the set of actions that the 51 client has in fact been granted. If the client only has a subset of the 52 requested access **it must not be considered an error** as it is not the 53 responsibility of the token server to indicate authorization errors as part of 54 this workflow. 55 56 Continuing with the example request, the token server will find that the 57 client's set of granted access to the repository is `[pull, push]` which when 58 intersected with the requested access `[pull, push]` yields an equal set. If 59 the granted access set was found only to be `[pull]` then the intersected set 60 would only be `[pull]`. If the client has no access to the repository then the 61 intersected set would be empty, `[]`. 62 63 It is this intersected set of access which is placed in the returned token. 64 65 The server will now construct a JSON Web Token to sign and return. A JSON Web 66 Token has 3 main parts: 67 68 1. Headers 69 70 The header of a JSON Web Token is a standard JOSE header. The "typ" field 71 will be "JWT" and it will also contain the "alg" which identifies the 72 signing algorithm used to produce the signature. It will also usually have 73 a "kid" field, the ID of the key which was used to sign the token. 74 75 Here is an example JOSE Header for a JSON Web Token (formatted with 76 whitespace for readability): 77 78 ``` 79 { 80 "typ": "JWT", 81 "alg": "ES256", 82 "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6" 83 } 84 ``` 85 86 It specifies that this object is going to be a JSON Web token signed using 87 the key with the given ID using the Elliptic Curve signature algorithm 88 using a SHA256 hash. 89 90 2. Claim Set 91 92 The Claim Set is a JSON struct containing these standard registered claim 93 name fields: 94 95 <dl> 96 <dt> 97 <code>iss</code> (Issuer) 98 </dt> 99 <dd> 100 The issuer of the token, typically the fqdn of the authorization 101 server. 102 </dd> 103 <dt> 104 <code>sub</code> (Subject) 105 </dt> 106 <dd> 107 The subject of the token; the name or id of the client which 108 requested it. This should be empty (`""`) if the client did not 109 authenticate. 110 </dd> 111 <dt> 112 <code>aud</code> (Audience) 113 </dt> 114 <dd> 115 The intended audience of the token; the name or id of the service 116 which will verify the token to authorize the client/subject. 117 </dd> 118 <dt> 119 <code>exp</code> (Expiration) 120 </dt> 121 <dd> 122 The token should only be considered valid up to this specified date 123 and time. 124 </dd> 125 <dt> 126 <code>nbf</code> (Not Before) 127 </dt> 128 <dd> 129 The token should not be considered valid before this specified date 130 and time. 131 </dd> 132 <dt> 133 <code>iat</code> (Issued At) 134 </dt> 135 <dd> 136 Specifies the date and time which the Authorization server 137 generated this token. 138 </dd> 139 <dt> 140 <code>jti</code> (JWT ID) 141 </dt> 142 <dd> 143 A unique identifier for this token. Can be used by the intended 144 audience to prevent replays of the token. 145 </dd> 146 </dl> 147 148 The Claim Set will also contain a private claim name unique to this 149 authorization server specification: 150 151 <dl> 152 <dt> 153 <code>access</code> 154 </dt> 155 <dd> 156 An array of access entry objects with the following fields: 157 158 <dl> 159 <dt> 160 <code>type</code> 161 </dt> 162 <dd> 163 The type of resource hosted by the service. 164 </dd> 165 <dt> 166 <code>name</code> 167 </dt> 168 <dd> 169 The name of the resource of the given type hosted by the 170 service. 171 </dd> 172 <dt> 173 <code>actions</code> 174 </dt> 175 <dd> 176 An array of strings which give the actions authorized on 177 this resource. 178 </dd> 179 </dl> 180 </dd> 181 </dl> 182 183 Here is an example of such a JWT Claim Set (formatted with whitespace for 184 readability): 185 186 ``` 187 { 188 "iss": "auth.docker.com", 189 "sub": "jlhawn", 190 "aud": "registry.docker.com", 191 "exp": 1415387315, 192 "nbf": 1415387015, 193 "iat": 1415387015, 194 "jti": "tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws", 195 "access": [ 196 { 197 "type": "repository", 198 "name": "samalba/my-app", 199 "actions": [ 200 "pull", 201 "push" 202 ] 203 } 204 ] 205 } 206 ``` 207 208 3. Signature 209 210 The authorization server will produce a JOSE header and Claim Set with no 211 extraneous whitespace, i.e., the JOSE Header from above would be 212 213 ``` 214 {"typ":"JWT","alg":"ES256","kid":"PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"} 215 ``` 216 217 and the Claim Set from above would be 218 219 ``` 220 {"iss":"auth.docker.com","sub":"jlhawn","aud":"registry.docker.com","exp":1415387315,"nbf":1415387015,"iat":1415387015,"jti":"tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws","access":[{"type":"repository","name":"samalba/my-app","actions":["push","pull"]}]} 221 ``` 222 223 The utf-8 representation of this JOSE header and Claim Set are then 224 url-safe base64 encoded (sans trailing '=' buffer), producing: 225 226 ``` 227 eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0 228 ``` 229 230 for the JOSE Header and 231 232 ``` 233 eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0 234 ``` 235 236 for the Claim Set. These two are concatenated using a '.' character, 237 yielding the string: 238 239 ``` 240 eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0 241 ``` 242 243 This is then used as the payload to a the `ES256` signature algorithm 244 specified in the JOSE header and specified fully in [Section 3.4 of the JSON Web Algorithms (JWA) 245 draft specification](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-38#section-3.4) 246 247 This example signature will use the following ECDSA key for the server: 248 249 ``` 250 { 251 "kty": "EC", 252 "crv": "P-256", 253 "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6", 254 "d": "R7OnbfMaD5J2jl7GeE8ESo7CnHSBm_1N2k9IXYFrKJA", 255 "x": "m7zUpx3b-zmVE5cymSs64POG9QcyEpJaYCD82-549_Q", 256 "y": "dU3biz8sZ_8GPB-odm8Wxz3lNDr1xcAQQPQaOcr1fmc" 257 } 258 ``` 259 260 A resulting signature of the above payload using this key is: 261 262 ``` 263 QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w 264 ``` 265 266 Concatenating all of these together with a `.` character gives the 267 resulting JWT: 268 269 ``` 270 eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w 271 ``` 272 273 This can now be placed in an HTTP response and returned to the client to use to 274 authenticate to the audience service: 275 276 277 ``` 278 HTTP/1.1 200 OK 279 Content-Type: application/json 280 281 {"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w"} 282 ``` 283 284 ## Using the signed token 285 286 Once the client has a token, it will try the registry request again with the 287 token placed in the HTTP `Authorization` header like so: 288 289 ``` 290 Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw 291 ``` 292 293 This is also described in [Section 2.1 of RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-2.1) 294 295 ## Verifying the token 296 297 The registry must now verify the token presented by the user by inspecting the 298 claim set within. The registry will: 299 300 - Ensure that the issuer (`iss` claim) is an authority it trusts. 301 - Ensure that the registry identifies as the audience (`aud` claim). 302 - Check that the current time is between the `nbf` and `exp` claim times. 303 - If enforcing single-use tokens, check that the JWT ID (`jti` claim) value has 304 not been seen before. 305 - To enforce this, the registry may keep a record of `jti`s it has seen for 306 up to the `exp` time of the token to prevent token replays. 307 - Check the `access` claim value and use the identified resources and the list 308 of actions authorized to determine whether the token grants the required 309 level of access for the operation the client is attempting to perform. 310 - Verify that the signature of the token is valid. 311 312 If any of these requirements are not met, the registry will return a 313 `403 Forbidden` response to indicate that the token is invalid. 314 315 **Note**: it is only at this point in the workflow that an authorization error 316 may occur. The token server should *not* return errors when the user does not 317 have the requested authorization. Instead, the returned token should indicate 318 whatever of the requested scope the client does have (the intersection of 319 requested and granted access). If the token does not supply proper 320 authorization then the registry will return the appropriate error. 321 322 At no point in this process should the registry need to call back to the 323 authorization server. The registry only needs to be supplied with the trusted 324 public keys to verify the token signatures.