oauth2.go raw

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