LEFT | RIGHT |
1 // Copyright 2012 The goauth2 Authors. All rights reserved. | 1 // Copyright 2012 The goauth2 Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // The jwt package provides support for creating credentials for OAuth2 service | 5 // The jwt package provides support for creating credentials for OAuth2 service |
6 // account requests. | 6 // account requests. |
7 // | 7 // |
8 // For examples of the package usage please see jwt_test.go. | 8 // For examples of the package usage please see jwt_test.go. |
9 // | 9 // |
10 // For info on OAuth2 service accounts please see the online documentation. | 10 // For info on OAuth2 service accounts please see the online documentation. |
(...skipping 13 matching lines...) Expand all Loading... |
24 "errors" | 24 "errors" |
25 "fmt" | 25 "fmt" |
26 "net/http" | 26 "net/http" |
27 "net/url" | 27 "net/url" |
28 "strings" | 28 "strings" |
29 "time" | 29 "time" |
30 | 30 |
31 "code.google.com/p/goauth2/oauth" | 31 "code.google.com/p/goauth2/oauth" |
32 ) | 32 ) |
33 | 33 |
| 34 // These are the default/standard values for this to work for Google service acc
ounts. |
34 const ( | 35 const ( |
35 stdAlgorithm = "RS256" | 36 stdAlgorithm = "RS256" |
36 stdType = "JWT" | 37 stdType = "JWT" |
37 stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer" | 38 stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer" |
38 stdGrantType = "assertion" | 39 stdGrantType = "assertion" |
39 ) | 40 ) |
40 | 41 |
41 var ( | 42 var ( |
42 ErrInvalidKey = errors.New("Invalid Key") | 43 ErrInvalidKey = errors.New("Invalid Key") |
43 | 44 |
44 StdHeader = &Header{ | 45 StdHeader = &Header{ |
45 Algorithm: stdAlgorithm, | 46 Algorithm: stdAlgorithm, |
46 Type: stdType, | 47 Type: stdType, |
47 } | 48 } |
48 ) | 49 ) |
49 | 50 |
50 // urlEncode returns and Base64url encoded version of the input string with any | 51 // urlEncode returns and Base64url encoded version of the input string with any |
51 // trailing "=" stripped. | 52 // trailing "=" stripped. |
52 func urlEncode(b []byte) string { | 53 func urlEncode(b []byte) string { |
53 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") | 54 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") |
54 } | 55 } |
55 | 56 |
56 // The header consists of two fields that indicate the signing algorithm and the | 57 // The header consists of two fields that indicate the signing algorithm and the |
57 // format of the assertion. Both fields are mandatory, and each field has only | 58 // format of the assertion. Both fields are mandatory, and each field has only |
58 // one value. As additional algorithms and formats are introduced, this header | 59 // one value. As additional algorithms and formats are introduced, this header |
59 // will change accordingly. | 60 // will change accordingly. |
60 // | 61 // |
61 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format. | 62 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format. |
62 type Header struct { | 63 type Header struct { |
63 » Algorithm string `json:"alg"` | 64 » Algorithm string `json:"alg"` // encoding algorithm (usually RSA SHA256) |
64 » Type string `json:"typ"` | 65 » Type string `json:"typ"` // token format (usually JWT) |
65 } | 66 } |
66 | 67 |
67 // Encode returns the Base64url encoded form of the Header. | 68 // Encode returns the Base64url encoded form of the Header. |
68 func (h *Header) Encode() (string, error) { | 69 func (h *Header) Encode() (string, error) { |
69 b, err := json.Marshal(h) | 70 b, err := json.Marshal(h) |
70 if err != nil { | 71 if err != nil { |
71 return "", err | 72 return "", err |
72 } | 73 } |
73 return urlEncode(b), nil | 74 return urlEncode(b), nil |
74 } | 75 } |
75 | 76 |
76 // The JWT claim set contains information about the JWT including the | 77 // The JWT claim set contains information about the JWT including the |
77 // permissions being requested (scopes), the target of the token, the issuer, | 78 // permissions being requested (scopes), the target of the token, the issuer, |
78 // the time the token was issued, and the lifetime of the token. Most of the | 79 // the time the token was issued, and the lifetime of the token. |
79 // fields are mandatory. | |
80 // | 80 // |
81 // Aud is usually https://accounts.google.com/o/oauth2/token | 81 // Aud is usually https://accounts.google.com/o/oauth2/token |
82 type ClaimSet struct { | 82 type ClaimSet struct { |
83 Iss string `json:"iss"` // email address of the client_id
of the application making the access token request | 83 Iss string `json:"iss"` // email address of the client_id
of the application making the access token request |
84 Scope string `json:"scope"` // space-delimited list of the per
missions the application requests | 84 Scope string `json:"scope"` // space-delimited list of the per
missions the application requests |
85 » Aud string `json:"aud"` // a descriptor of the intended ta
rget of the assertion | 85 » Aud string `json:"aud"` // descriptor of the intended targ
et of the assertion |
86 » Exp int64 `json:"exp"` // the expiration time of the asse
rtion (maximum of 1 hour from the JWT encoding time) | 86 » Exp int64 `json:"exp"` // expiration time of the assertio
n (Optional; maximum of 1 hour from the JWT encoding time) |
87 » Iat int64 `json:"iat"` // the time the assertion was issu
ed (the time that the JWT was encoded) | 87 » Iat int64 `json:"iat"` // time the assertion was issued (
Optional; time that the JWT was encoded) |
88 » Prn string `json:"prn,omitempty"` // the email for which the applica
tion is requesting delegated access (Optional). | 88 » Prn string `json:"prn,omitempty"` // email for which the application
is requesting delegated access (Optional). |
89 expTime time.Time | 89 expTime time.Time |
90 iatTime time.Time | 90 iatTime time.Time |
91 } | 91 } |
92 | 92 |
93 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively. | 93 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively. |
94 // | 94 // |
95 // Note that these times have nothing to do with the expiration time for the | 95 // Note that these times have nothing to do with the expiration time for the |
96 // access_token returned by the server. These have to do with the lifetime of | 96 // access_token returned by the server. These have to do with the lifetime of |
97 // the encoded JWT. | 97 // the encoded JWT. |
98 // | 98 // |
(...skipping 29 matching lines...) Expand all Loading... |
128 // Encode returns the Base64url encoded form of the Signature. The error | 128 // Encode returns the Base64url encoded form of the Signature. The error |
129 // returned from this function is always nil and can be safely ignored. | 129 // returned from this function is always nil and can be safely ignored. |
130 func (s Signature) Encode() (string, error) { | 130 func (s Signature) Encode() (string, error) { |
131 return urlEncode([]byte(s)), nil | 131 return urlEncode([]byte(s)), nil |
132 } | 132 } |
133 | 133 |
134 // A JWT is composed of three parts: a header, a claim set, and a signature. | 134 // A JWT is composed of three parts: a header, a claim set, and a signature. |
135 // The well formed and encoded JWT can then be exchanged for an access token. | 135 // The well formed and encoded JWT can then be exchanged for an access token. |
136 // | 136 // |
137 // The Token is not a JWT, but is is encoded to produce a well formed JWT. | 137 // The Token is not a JWT, but is is encoded to produce a well formed JWT. |
| 138 // |
| 139 // When obtaining a key from the Google API console it will be downloaded in a |
| 140 // PKCS12 encoding. To use this key you will need to convert it to a PEM file. |
| 141 // This can be achieved on a with openssl. |
| 142 // |
| 143 // $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <
key.pem> |
| 144 // |
| 145 // The contents of this file can then be used as the Key. |
138 type Token struct { | 146 type Token struct { |
139 » Header *Header | 147 » Header *Header // if nil the StdHeader will be used |
140 » ClaimSet *ClaimSet | 148 » ClaimSet *ClaimSet // claim set used to construct the JWT |
141 » Signature Signature | 149 » Signature Signature // generally computed in Encode based on the ClaimSe
t |
142 » Key []byte | 150 » Key []byte // PEM printable encoding of the private key |
143 pKey *rsa.PrivateKey | 151 pKey *rsa.PrivateKey |
144 head string | 152 head string |
145 claim string | 153 claim string |
146 sig string | 154 sig string |
| 155 } |
| 156 |
| 157 // NewToken returns a filled in *Token based on the StdHeader, and sets the Iat |
| 158 // and Exp times based on when the call to Assert is made. |
| 159 func NewToken(iss, scope, aud string, key []byte) *Token { |
| 160 c := &ClaimSet{ |
| 161 Iss: iss, |
| 162 Scope: scope, |
| 163 Aud: aud, |
| 164 } |
| 165 t := &Token{ |
| 166 Header: StdHeader, |
| 167 ClaimSet: c, |
| 168 Key: key, |
| 169 } |
| 170 return t |
147 } | 171 } |
148 | 172 |
149 // Expired returns a boolean value letting us know if the token has expired. | 173 // Expired returns a boolean value letting us know if the token has expired. |
150 func (t *Token) Expired() bool { | 174 func (t *Token) Expired() bool { |
151 // The truth is that if the sis is empty, then it was never encoded | 175 // The truth is that if the sis is empty, then it was never encoded |
152 // and was never sent to the server, so you should re-encode and send it | 176 // and was never sent to the server, so you should re-encode and send it |
153 // anyways. | 177 // anyways. |
154 if t.ClaimSet.Exp == 0 { | 178 if t.ClaimSet.Exp == 0 { |
155 return true | 179 return true |
156 } | 180 } |
157 return t.ClaimSet.expTime.Before(time.Now()) | 181 return t.ClaimSet.expTime.Before(time.Now()) |
158 } | 182 } |
159 | 183 |
160 // AssertionType returns the standard assertion type for a JWT. | 184 // AssertionType returns the standard assertion type for a JWT. |
161 func (t *Token) AssertionType() string { | 185 func (t *Token) AssertionType() string { |
162 return stdAssertionType | 186 return stdAssertionType |
163 } | 187 } |
164 | 188 |
165 // Encode constructs and signs a Token returning a JWT ready to use for | 189 // Encode constructs and signs a Token returning a JWT ready to use for |
166 // requesting an access token. | 190 // requesting an access token. |
167 func (t *Token) Encode() (string, error) { | 191 func (t *Token) Encode() (string, error) { |
168 var tok string | 192 var tok string |
169 var err error | 193 var err error |
| 194 if t.Header == nil { |
| 195 t.Header = StdHeader |
| 196 } |
170 t.head, err = t.Header.Encode() | 197 t.head, err = t.Header.Encode() |
171 if err != nil { | 198 if err != nil { |
172 return tok, err | 199 return tok, err |
173 } | 200 } |
174 t.claim, err = t.ClaimSet.Encode() | 201 t.claim, err = t.ClaimSet.Encode() |
175 if err != nil { | 202 if err != nil { |
176 return tok, err | 203 return tok, err |
177 } | 204 } |
178 err = t.sign() | 205 err = t.sign() |
179 if err != nil { | 206 if err != nil { |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
222 if !ok { | 249 if !ok { |
223 return ErrInvalidKey | 250 return ErrInvalidKey |
224 } | 251 } |
225 return nil | 252 return nil |
226 } | 253 } |
227 | 254 |
228 // Assert obtains an *oauth.Token from the remote server by encoding and sending | 255 // Assert obtains an *oauth.Token from the remote server by encoding and sending |
229 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be | 256 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be |
230 // refreshed (no refresh_token is returned with the response). Once this token | 257 // refreshed (no refresh_token is returned with the response). Once this token |
231 // expires call this method again to get a fresh one. | 258 // expires call this method again to get a fresh one. |
232 func (t *Token) Assert() (*oauth.Token, error) { | 259 func (t *Token) Assert(c *http.Client) (*oauth.Token, error) { |
233 var o *oauth.Token | 260 var o *oauth.Token |
234 » req, err := t.buildRequest() | 261 » u, v, err := t.buildRequest() |
235 if err != nil { | 262 if err != nil { |
236 return o, err | 263 return o, err |
237 } | 264 } |
238 » hc := &http.Client{} | 265 » resp, err := c.PostForm(u, v) |
239 » resp, err := hc.Do(req) | |
240 if err != nil { | 266 if err != nil { |
241 return o, err | 267 return o, err |
242 } | 268 } |
243 o, err = handleResponse(resp) | 269 o, err = handleResponse(resp) |
244 return o, err | 270 return o, err |
245 } | 271 } |
246 | 272 |
247 // BuildRequest returns and *http.Request that can be used to make an access | 273 // buildRequest sets up the URL values and the proper URL string for making our |
248 // token request. Per the Assertion profile in OAuth 2.0 specification, this | 274 // access_token request. |
249 // access token request is an HTTPs POST. | 275 func (t *Token) buildRequest() (string, url.Values, error) { |
250 func (t *Token) buildRequest() (*http.Request, error) { | 276 » v := url.Values{} |
251 j, err := t.Encode() | 277 j, err := t.Encode() |
252 if err != nil { | 278 if err != nil { |
253 » » return nil, err | 279 » » return t.ClaimSet.Aud, v, err |
254 » } | 280 » } |
255 » v := url.Values{} | |
256 v.Set("grant_type", stdGrantType) | 281 v.Set("grant_type", stdGrantType) |
257 v.Set("assertion_type", t.AssertionType()) | 282 v.Set("assertion_type", t.AssertionType()) |
258 v.Set("assertion", j) | 283 v.Set("assertion", j) |
259 » u, err := url.Parse(t.ClaimSet.Aud) | 284 » return t.ClaimSet.Aud, v, nil |
260 » if err != nil { | |
261 » » return nil, err | |
262 » } | |
263 » u.RawQuery = v.Encode() | |
264 » return http.NewRequest("POST", u.String(), nil) | |
265 } | 285 } |
266 | 286 |
267 // Used for decoding the response body. | 287 // Used for decoding the response body. |
268 type respBody struct { | 288 type respBody struct { |
269 Access string `json:"access_token"` | 289 Access string `json:"access_token"` |
270 Type string `json:"token_type"` | 290 Type string `json:"token_type"` |
271 ExpiresIn time.Duration `json:"expires_in"` | 291 ExpiresIn time.Duration `json:"expires_in"` |
272 } | 292 } |
273 | 293 |
274 // HandleResponse returns an *oauth.Token given the *http.Response from a | 294 // handleResponse returns a filled in *oauth.Token given the *http.Response from |
275 // *http.Request created by BuildRequest. | 295 // a *http.Request created by buildRequest. |
276 func handleResponse(r *http.Response) (*oauth.Token, error) { | 296 func handleResponse(r *http.Response) (*oauth.Token, error) { |
277 o := &oauth.Token{} | 297 o := &oauth.Token{} |
278 defer r.Body.Close() | 298 defer r.Body.Close() |
279 if r.StatusCode != 200 { | 299 if r.StatusCode != 200 { |
280 return o, errors.New("invalid response: " + r.Status) | 300 return o, errors.New("invalid response: " + r.Status) |
281 } | 301 } |
282 b := &respBody{} | 302 b := &respBody{} |
283 err := json.NewDecoder(r.Body).Decode(b) | 303 err := json.NewDecoder(r.Body).Decode(b) |
284 if err != nil { | 304 if err != nil { |
285 return o, err | 305 return o, err |
286 } | 306 } |
287 o.AccessToken = b.Access | 307 o.AccessToken = b.Access |
288 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second) | 308 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second) |
289 return o, nil | 309 return o, nil |
290 } | 310 } |
LEFT | RIGHT |