oauth2.go (13478B)
1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package oauth2 provides support for making 6 // OAuth2 authorized and authenticated HTTP requests, 7 // as specified in RFC 6749. 8 // It can additionally grant authorization with Bearer JWT. 9 package oauth2 // import "golang.org/x/oauth2" 10 11 import ( 12 "bytes" 13 "context" 14 "errors" 15 "net/http" 16 "net/url" 17 "strings" 18 "sync" 19 "time" 20 21 "golang.org/x/oauth2/internal" 22 ) 23 24 // NoContext is the default context you should supply if not using 25 // your own context.Context (see https://golang.org/x/net/context). 26 // 27 // Deprecated: Use context.Background() or context.TODO() instead. 28 var NoContext = context.TODO() 29 30 // RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op. 31 // 32 // Deprecated: this function no longer does anything. Caller code that 33 // wants to avoid potential extra HTTP requests made during 34 // auto-probing of the provider's auth style should set 35 // Endpoint.AuthStyle. 36 func RegisterBrokenAuthHeaderProvider(tokenURL string) {} 37 38 // Config describes a typical 3-legged OAuth2 flow, with both the 39 // client application information and the server's endpoint URLs. 40 // For the client credentials 2-legged OAuth2 flow, see the clientcredentials 41 // package (https://golang.org/x/oauth2/clientcredentials). 42 type Config struct { 43 // ClientID is the application's ID. 44 ClientID string 45 46 // ClientSecret is the application's secret. 47 ClientSecret string 48 49 // Endpoint contains the resource server's token endpoint 50 // URLs. These are constants specific to each server and are 51 // often available via site-specific packages, such as 52 // google.Endpoint or github.Endpoint. 53 Endpoint Endpoint 54 55 // RedirectURL is the URL to redirect users going through 56 // the OAuth flow, after the resource owner's URLs. 57 RedirectURL string 58 59 // Scope specifies optional requested permissions. 60 Scopes []string 61 } 62 63 // A TokenSource is anything that can return a token. 64 type TokenSource interface { 65 // Token returns a token or an error. 66 // Token must be safe for concurrent use by multiple goroutines. 67 // The returned Token must not be modified. 68 Token() (*Token, error) 69 } 70 71 // Endpoint represents an OAuth 2.0 provider's authorization and token 72 // endpoint URLs. 73 type Endpoint struct { 74 AuthURL string 75 TokenURL string 76 77 // AuthStyle optionally specifies how the endpoint wants the 78 // client ID & client secret sent. The zero value means to 79 // auto-detect. 80 AuthStyle AuthStyle 81 } 82 83 // AuthStyle represents how requests for tokens are authenticated 84 // to the server. 85 type AuthStyle int 86 87 const ( 88 // AuthStyleAutoDetect means to auto-detect which authentication 89 // style the provider wants by trying both ways and caching 90 // the successful way for the future. 91 AuthStyleAutoDetect AuthStyle = 0 92 93 // AuthStyleInParams sends the "client_id" and "client_secret" 94 // in the POST body as application/x-www-form-urlencoded parameters. 95 AuthStyleInParams AuthStyle = 1 96 97 // AuthStyleInHeader sends the client_id and client_password 98 // using HTTP Basic Authorization. This is an optional style 99 // described in the OAuth2 RFC 6749 section 2.3.1. 100 AuthStyleInHeader AuthStyle = 2 101 ) 102 103 var ( 104 // AccessTypeOnline and AccessTypeOffline are options passed 105 // to the Options.AuthCodeURL method. They modify the 106 // "access_type" field that gets sent in the URL returned by 107 // AuthCodeURL. 108 // 109 // Online is the default if neither is specified. If your 110 // application needs to refresh access tokens when the user 111 // is not present at the browser, then use offline. This will 112 // result in your application obtaining a refresh token the 113 // first time your application exchanges an authorization 114 // code for a user. 115 AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online") 116 AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline") 117 118 // ApprovalForce forces the users to view the consent dialog 119 // and confirm the permissions request at the URL returned 120 // from AuthCodeURL, even if they've already done so. 121 ApprovalForce AuthCodeOption = SetAuthURLParam("prompt", "consent") 122 ) 123 124 // An AuthCodeOption is passed to Config.AuthCodeURL. 125 type AuthCodeOption interface { 126 setValue(url.Values) 127 } 128 129 type setParam struct{ k, v string } 130 131 func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } 132 133 // SetAuthURLParam builds an AuthCodeOption which passes key/value parameters 134 // to a provider's authorization endpoint. 135 func SetAuthURLParam(key, value string) AuthCodeOption { 136 return setParam{key, value} 137 } 138 139 // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page 140 // that asks for permissions for the required scopes explicitly. 141 // 142 // State is a token to protect the user from CSRF attacks. You must 143 // always provide a non-empty string and validate that it matches the 144 // state query parameter on your redirect callback. 145 // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. 146 // 147 // Opts may include AccessTypeOnline or AccessTypeOffline, as well 148 // as ApprovalForce. 149 // It can also be used to pass the PKCE challenge. 150 // See https://www.oauth.com/oauth2-servers/pkce/ for more info. 151 func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { 152 var buf bytes.Buffer 153 buf.WriteString(c.Endpoint.AuthURL) 154 v := url.Values{ 155 "response_type": {"code"}, 156 "client_id": {c.ClientID}, 157 } 158 if c.RedirectURL != "" { 159 v.Set("redirect_uri", c.RedirectURL) 160 } 161 if len(c.Scopes) > 0 { 162 v.Set("scope", strings.Join(c.Scopes, " ")) 163 } 164 if state != "" { 165 // TODO(light): Docs say never to omit state; don't allow empty. 166 v.Set("state", state) 167 } 168 for _, opt := range opts { 169 opt.setValue(v) 170 } 171 if strings.Contains(c.Endpoint.AuthURL, "?") { 172 buf.WriteByte('&') 173 } else { 174 buf.WriteByte('?') 175 } 176 buf.WriteString(v.Encode()) 177 return buf.String() 178 } 179 180 // PasswordCredentialsToken converts a resource owner username and password 181 // pair into a token. 182 // 183 // Per the RFC, this grant type should only be used "when there is a high 184 // degree of trust between the resource owner and the client (e.g., the client 185 // is part of the device operating system or a highly privileged application), 186 // and when other authorization grant types are not available." 187 // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. 188 // 189 // The provided context optionally controls which HTTP client is used. See the HTTPClient variable. 190 func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { 191 v := url.Values{ 192 "grant_type": {"password"}, 193 "username": {username}, 194 "password": {password}, 195 } 196 if len(c.Scopes) > 0 { 197 v.Set("scope", strings.Join(c.Scopes, " ")) 198 } 199 return retrieveToken(ctx, c, v) 200 } 201 202 // Exchange converts an authorization code into a token. 203 // 204 // It is used after a resource provider redirects the user back 205 // to the Redirect URI (the URL obtained from AuthCodeURL). 206 // 207 // The provided context optionally controls which HTTP client is used. See the HTTPClient variable. 208 // 209 // The code will be in the *http.Request.FormValue("code"). Before 210 // calling Exchange, be sure to validate FormValue("state"). 211 // 212 // Opts may include the PKCE verifier code if previously used in AuthCodeURL. 213 // See https://www.oauth.com/oauth2-servers/pkce/ for more info. 214 func (c *Config) Exchange(ctx context.Context, code string, opts ...AuthCodeOption) (*Token, error) { 215 v := url.Values{ 216 "grant_type": {"authorization_code"}, 217 "code": {code}, 218 } 219 if c.RedirectURL != "" { 220 v.Set("redirect_uri", c.RedirectURL) 221 } 222 for _, opt := range opts { 223 opt.setValue(v) 224 } 225 return retrieveToken(ctx, c, v) 226 } 227 228 // Client returns an HTTP client using the provided token. 229 // The token will auto-refresh as necessary. The underlying 230 // HTTP transport will be obtained using the provided context. 231 // The returned client and its Transport should not be modified. 232 func (c *Config) Client(ctx context.Context, t *Token) *http.Client { 233 return NewClient(ctx, c.TokenSource(ctx, t)) 234 } 235 236 // TokenSource returns a TokenSource that returns t until t expires, 237 // automatically refreshing it as necessary using the provided context. 238 // 239 // Most users will use Config.Client instead. 240 func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource { 241 tkr := &tokenRefresher{ 242 ctx: ctx, 243 conf: c, 244 } 245 if t != nil { 246 tkr.refreshToken = t.RefreshToken 247 } 248 return &reuseTokenSource{ 249 t: t, 250 new: tkr, 251 } 252 } 253 254 // tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token" 255 // HTTP requests to renew a token using a RefreshToken. 256 type tokenRefresher struct { 257 ctx context.Context // used to get HTTP requests 258 conf *Config 259 refreshToken string 260 } 261 262 // WARNING: Token is not safe for concurrent access, as it 263 // updates the tokenRefresher's refreshToken field. 264 // Within this package, it is used by reuseTokenSource which 265 // synchronizes calls to this method with its own mutex. 266 func (tf *tokenRefresher) Token() (*Token, error) { 267 if tf.refreshToken == "" { 268 return nil, errors.New("oauth2: token expired and refresh token is not set") 269 } 270 271 tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{ 272 "grant_type": {"refresh_token"}, 273 "refresh_token": {tf.refreshToken}, 274 }) 275 276 if err != nil { 277 return nil, err 278 } 279 if tf.refreshToken != tk.RefreshToken { 280 tf.refreshToken = tk.RefreshToken 281 } 282 return tk, err 283 } 284 285 // reuseTokenSource is a TokenSource that holds a single token in memory 286 // and validates its expiry before each call to retrieve it with 287 // Token. If it's expired, it will be auto-refreshed using the 288 // new TokenSource. 289 type reuseTokenSource struct { 290 new TokenSource // called when t is expired. 291 292 mu sync.Mutex // guards t 293 t *Token 294 295 expiryDelta time.Duration 296 } 297 298 // Token returns the current token if it's still valid, else will 299 // refresh the current token (using r.Context for HTTP client 300 // information) and return the new one. 301 func (s *reuseTokenSource) Token() (*Token, error) { 302 s.mu.Lock() 303 defer s.mu.Unlock() 304 if s.t.Valid() { 305 return s.t, nil 306 } 307 t, err := s.new.Token() 308 if err != nil { 309 return nil, err 310 } 311 t.expiryDelta = s.expiryDelta 312 s.t = t 313 return t, nil 314 } 315 316 // StaticTokenSource returns a TokenSource that always returns the same token. 317 // Because the provided token t is never refreshed, StaticTokenSource is only 318 // useful for tokens that never expire. 319 func StaticTokenSource(t *Token) TokenSource { 320 return staticTokenSource{t} 321 } 322 323 // staticTokenSource is a TokenSource that always returns the same Token. 324 type staticTokenSource struct { 325 t *Token 326 } 327 328 func (s staticTokenSource) Token() (*Token, error) { 329 return s.t, nil 330 } 331 332 // HTTPClient is the context key to use with golang.org/x/net/context's 333 // WithValue function to associate an *http.Client value with a context. 334 var HTTPClient internal.ContextKey 335 336 // NewClient creates an *http.Client from a Context and TokenSource. 337 // The returned client is not valid beyond the lifetime of the context. 338 // 339 // Note that if a custom *http.Client is provided via the Context it 340 // is used only for token acquisition and is not used to configure the 341 // *http.Client returned from NewClient. 342 // 343 // As a special case, if src is nil, a non-OAuth2 client is returned 344 // using the provided context. This exists to support related OAuth2 345 // packages. 346 func NewClient(ctx context.Context, src TokenSource) *http.Client { 347 if src == nil { 348 return internal.ContextClient(ctx) 349 } 350 return &http.Client{ 351 Transport: &Transport{ 352 Base: internal.ContextClient(ctx).Transport, 353 Source: ReuseTokenSource(nil, src), 354 }, 355 } 356 } 357 358 // ReuseTokenSource returns a TokenSource which repeatedly returns the 359 // same token as long as it's valid, starting with t. 360 // When its cached token is invalid, a new token is obtained from src. 361 // 362 // ReuseTokenSource is typically used to reuse tokens from a cache 363 // (such as a file on disk) between runs of a program, rather than 364 // obtaining new tokens unnecessarily. 365 // 366 // The initial token t may be nil, in which case the TokenSource is 367 // wrapped in a caching version if it isn't one already. This also 368 // means it's always safe to wrap ReuseTokenSource around any other 369 // TokenSource without adverse effects. 370 func ReuseTokenSource(t *Token, src TokenSource) TokenSource { 371 // Don't wrap a reuseTokenSource in itself. That would work, 372 // but cause an unnecessary number of mutex operations. 373 // Just build the equivalent one. 374 if rt, ok := src.(*reuseTokenSource); ok { 375 if t == nil { 376 // Just use it directly. 377 return rt 378 } 379 src = rt.new 380 } 381 return &reuseTokenSource{ 382 t: t, 383 new: src, 384 } 385 } 386 387 // ReuseTokenSource returns a TokenSource that acts in the same manner as the 388 // TokenSource returned by ReuseTokenSource, except the expiry buffer is 389 // configurable. The expiration time of a token is calculated as 390 // t.Expiry.Add(-earlyExpiry). 391 func ReuseTokenSourceWithExpiry(t *Token, src TokenSource, earlyExpiry time.Duration) TokenSource { 392 // Don't wrap a reuseTokenSource in itself. That would work, 393 // but cause an unnecessary number of mutex operations. 394 // Just build the equivalent one. 395 if rt, ok := src.(*reuseTokenSource); ok { 396 if t == nil { 397 // Just use it directly, but set the expiryDelta to earlyExpiry, 398 // so the behavior matches what the user expects. 399 rt.expiryDelta = earlyExpiry 400 return rt 401 } 402 src = rt.new 403 } 404 if t != nil { 405 t.expiryDelta = earlyExpiry 406 } 407 return &reuseTokenSource{ 408 t: t, 409 new: src, 410 expiryDelta: earlyExpiry, 411 } 412 }