| // Copyright 2014 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package oauth2 provides support for making | |
| // OAuth2 authorized and authenticated HTTP requests, | |
| // as specified in RFC 6749. | |
| // It can additionally grant authorization with Bearer JWT. | |
| package oauth2 // import "golang.org/x/oauth2" | |
| import ( | |
| "bytes" | |
| "context" | |
| "errors" | |
| "net/http" | |
| "net/url" | |
| "strings" | |
| "sync" | |
| "time" | |
| "golang.org/x/oauth2/internal" | |
| ) | |
| // NoContext is the default context you should supply if not using | |
| // your own context.Context (see https://golang.org/x/net/context). | |
| // | |
| // Deprecated: Use context.Background() or context.TODO() instead. | |
| var NoContext = context.TODO() | |
| // RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op. | |
| // | |
| // Deprecated: this function no longer does anything. Caller code that | |
| // wants to avoid potential extra HTTP requests made during | |
| // auto-probing of the provider's auth style should set | |
| // Endpoint.AuthStyle. | |
| func RegisterBrokenAuthHeaderProvider(tokenURL string) {} | |
| // Config describes a typical 3-legged OAuth2 flow, with both the | |
| // client application information and the server's endpoint URLs. | |
| // For the client credentials 2-legged OAuth2 flow, see the clientcredentials | |
| // package (https://golang.org/x/oauth2/clientcredentials). | |
| type Config struct { | |
| // ClientID is the application's ID. | |
| ClientID string | |
| // ClientSecret is the application's secret. | |
| ClientSecret string | |
| // Endpoint contains the resource server's token endpoint | |
| // URLs. These are constants specific to each server and are | |
| // often available via site-specific packages, such as | |
| // google.Endpoint or github.Endpoint. | |
| Endpoint Endpoint | |
| // RedirectURL is the URL to redirect users going through | |
| // the OAuth flow, after the resource owner's URLs. | |
| RedirectURL string | |
| // Scope specifies optional requested permissions. | |
| Scopes []string | |
| // authStyleCache caches which auth style to use when Endpoint.AuthStyle is | |
| // the zero value (AuthStyleAutoDetect). | |
| authStyleCache internal.LazyAuthStyleCache | |
| } | |
| // A TokenSource is anything that can return a token. | |
| type TokenSource interface { | |
| // Token returns a token or an error. | |
| // Token must be safe for concurrent use by multiple goroutines. | |
| // The returned Token must not be modified. | |
| Token() (*Token, error) | |
| } | |
| // Endpoint represents an OAuth 2.0 provider's authorization and token | |
| // endpoint URLs. | |
| type Endpoint struct { | |
| AuthURL string | |
| DeviceAuthURL string | |
| TokenURL string | |
| // AuthStyle optionally specifies how the endpoint wants the | |
| // client ID & client secret sent. The zero value means to | |
| // auto-detect. | |
| AuthStyle AuthStyle | |
| } | |
| // AuthStyle represents how requests for tokens are authenticated | |
| // to the server. | |
| type AuthStyle int | |
| const ( | |
| // AuthStyleAutoDetect means to auto-detect which authentication | |
| // style the provider wants by trying both ways and caching | |
| // the successful way for the future. | |
| AuthStyleAutoDetect AuthStyle = 0 | |
| // AuthStyleInParams sends the "client_id" and "client_secret" | |
| // in the POST body as application/x-www-form-urlencoded parameters. | |
| AuthStyleInParams AuthStyle = 1 | |
| // AuthStyleInHeader sends the client_id and client_password | |
| // using HTTP Basic Authorization. This is an optional style | |
| // described in the OAuth2 RFC 6749 section 2.3.1. | |
| AuthStyleInHeader AuthStyle = 2 | |
| ) | |
| var ( | |
| // AccessTypeOnline and AccessTypeOffline are options passed | |
| // to the Options.AuthCodeURL method. They modify the | |
| // "access_type" field that gets sent in the URL returned by | |
| // AuthCodeURL. | |
| // | |
| // Online is the default if neither is specified. If your | |
| // application needs to refresh access tokens when the user | |
| // is not present at the browser, then use offline. This will | |
| // result in your application obtaining a refresh token the | |
| // first time your application exchanges an authorization | |
| // code for a user. | |
| AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online") | |
| AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline") | |
| // ApprovalForce forces the users to view the consent dialog | |
| // and confirm the permissions request at the URL returned | |
| // from AuthCodeURL, even if they've already done so. | |
| ApprovalForce AuthCodeOption = SetAuthURLParam("prompt", "consent") | |
| ) | |
| // An AuthCodeOption is passed to Config.AuthCodeURL. | |
| type AuthCodeOption interface { | |
| setValue(url.Values) | |
| } | |
| type setParam struct{ k, v string } | |
| func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } | |
| // SetAuthURLParam builds an AuthCodeOption which passes key/value parameters | |
| // to a provider's authorization endpoint. | |
| func SetAuthURLParam(key, value string) AuthCodeOption { | |
| return setParam{key, value} | |
| } | |
| // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page | |
| // that asks for permissions for the required scopes explicitly. | |
| // | |
| // State is an opaque value used by the client to maintain state between the | |
| // request and callback. The authorization server includes this value when | |
| // redirecting the user agent back to the client. | |
| // | |
| // Opts may include AccessTypeOnline or AccessTypeOffline, as well | |
| // as ApprovalForce. | |
| // | |
| // To protect against CSRF attacks, opts should include a PKCE challenge | |
| // (S256ChallengeOption). Not all servers support PKCE. An alternative is to | |
| // generate a random state parameter and verify it after exchange. | |
| // See https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 (predating | |
| // PKCE), https://www.oauth.com/oauth2-servers/pkce/ and | |
| // https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-09.html#name-cross-site-request-forgery (describing both approaches) | |
| func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { | |
| var buf bytes.Buffer | |
| buf.WriteString(c.Endpoint.AuthURL) | |
| v := url.Values{ | |
| "response_type": {"code"}, | |
| "client_id": {c.ClientID}, | |
| } | |
| if c.RedirectURL != "" { | |
| v.Set("redirect_uri", c.RedirectURL) | |
| } | |
| if len(c.Scopes) > 0 { | |
| v.Set("scope", strings.Join(c.Scopes, " ")) | |
| } | |
| if state != "" { | |
| v.Set("state", state) | |
| } | |
| for _, opt := range opts { | |
| opt.setValue(v) | |
| } | |
| if strings.Contains(c.Endpoint.AuthURL, "?") { | |
| buf.WriteByte('&') | |
| } else { | |
| buf.WriteByte('?') | |
| } | |
| buf.WriteString(v.Encode()) | |
| return buf.String() | |
| } | |
| // PasswordCredentialsToken converts a resource owner username and password | |
| // pair into a token. | |
| // | |
| // Per the RFC, this grant type should only be used "when there is a high | |
| // degree of trust between the resource owner and the client (e.g., the client | |
| // is part of the device operating system or a highly privileged application), | |
| // and when other authorization grant types are not available." | |
| // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. | |
| // | |
| // The provided context optionally controls which HTTP client is used. See the HTTPClient variable. | |
| func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { | |
| v := url.Values{ | |
| "grant_type": {"password"}, | |
| "username": {username}, | |
| "password": {password}, | |
| } | |
| if len(c.Scopes) > 0 { | |
| v.Set("scope", strings.Join(c.Scopes, " ")) | |
| } | |
| return retrieveToken(ctx, c, v) | |
| } | |
| // Exchange converts an authorization code into a token. | |
| // | |
| // It is used after a resource provider redirects the user back | |
| // to the Redirect URI (the URL obtained from AuthCodeURL). | |
| // | |
| // The provided context optionally controls which HTTP client is used. See the HTTPClient variable. | |
| // | |
| // The code will be in the *http.Request.FormValue("code"). Before | |
| // calling Exchange, be sure to validate FormValue("state") if you are | |
| // using it to protect against CSRF attacks. | |
| // | |
| // If using PKCE to protect against CSRF attacks, opts should include a | |
| // VerifierOption. | |
| func (c *Config) Exchange(ctx context.Context, code string, opts ...AuthCodeOption) (*Token, error) { | |
| v := url.Values{ | |
| "grant_type": {"authorization_code"}, | |
| "code": {code}, | |
| } | |
| if c.RedirectURL != "" { | |
| v.Set("redirect_uri", c.RedirectURL) | |
| } | |
| for _, opt := range opts { | |
| opt.setValue(v) | |
| } | |
| return retrieveToken(ctx, c, v) | |
| } | |
| // Client returns an HTTP client using the provided token. | |
| // The token will auto-refresh as necessary. The underlying | |
| // HTTP transport will be obtained using the provided context. | |
| // The returned client and its Transport should not be modified. | |
| func (c *Config) Client(ctx context.Context, t *Token) *http.Client { | |
| return NewClient(ctx, c.TokenSource(ctx, t)) | |
| } | |
| // TokenSource returns a TokenSource that returns t until t expires, | |
| // automatically refreshing it as necessary using the provided context. | |
| // | |
| // Most users will use Config.Client instead. | |
| func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource { | |
| tkr := &tokenRefresher{ | |
| ctx: ctx, | |
| conf: c, | |
| } | |
| if t != nil { | |
| tkr.refreshToken = t.RefreshToken | |
| } | |
| return &reuseTokenSource{ | |
| t: t, | |
| new: tkr, | |
| } | |
| } | |
| // tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token" | |
| // HTTP requests to renew a token using a RefreshToken. | |
| type tokenRefresher struct { | |
| ctx context.Context // used to get HTTP requests | |
| conf *Config | |
| refreshToken string | |
| } | |
| // WARNING: Token is not safe for concurrent access, as it | |
| // updates the tokenRefresher's refreshToken field. | |
| // Within this package, it is used by reuseTokenSource which | |
| // synchronizes calls to this method with its own mutex. | |
| func (tf *tokenRefresher) Token() (*Token, error) { | |
| if tf.refreshToken == "" { | |
| return nil, errors.New("oauth2: token expired and refresh token is not set") | |
| } | |
| tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{ | |
| "grant_type": {"refresh_token"}, | |
| "refresh_token": {tf.refreshToken}, | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if tf.refreshToken != tk.RefreshToken { | |
| tf.refreshToken = tk.RefreshToken | |
| } | |
| return tk, err | |
| } | |
| // reuseTokenSource is a TokenSource that holds a single token in memory | |
| // and validates its expiry before each call to retrieve it with | |
| // Token. If it's expired, it will be auto-refreshed using the | |
| // new TokenSource. | |
| type reuseTokenSource struct { | |
| new TokenSource // called when t is expired. | |
| mu sync.Mutex // guards t | |
| t *Token | |
| expiryDelta time.Duration | |
| } | |
| // Token returns the current token if it's still valid, else will | |
| // refresh the current token (using r.Context for HTTP client | |
| // information) and return the new one. | |
| func (s *reuseTokenSource) Token() (*Token, error) { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| if s.t.Valid() { | |
| return s.t, nil | |
| } | |
| t, err := s.new.Token() | |
| if err != nil { | |
| return nil, err | |
| } | |
| t.expiryDelta = s.expiryDelta | |
| s.t = t | |
| return t, nil | |
| } | |
| // StaticTokenSource returns a TokenSource that always returns the same token. | |
| // Because the provided token t is never refreshed, StaticTokenSource is only | |
| // useful for tokens that never expire. | |
| func StaticTokenSource(t *Token) TokenSource { | |
| return staticTokenSource{t} | |
| } | |
| // staticTokenSource is a TokenSource that always returns the same Token. | |
| type staticTokenSource struct { | |
| t *Token | |
| } | |
| func (s staticTokenSource) Token() (*Token, error) { | |
| return s.t, nil | |
| } | |
| // HTTPClient is the context key to use with golang.org/x/net/context's | |
| // WithValue function to associate an *http.Client value with a context. | |
| var HTTPClient internal.ContextKey | |
| // NewClient creates an *http.Client from a Context and TokenSource. | |
| // The returned client is not valid beyond the lifetime of the context. | |
| // | |
| // Note that if a custom *http.Client is provided via the Context it | |
| // is used only for token acquisition and is not used to configure the | |
| // *http.Client returned from NewClient. | |
| // | |
| // As a special case, if src is nil, a non-OAuth2 client is returned | |
| // using the provided context. This exists to support related OAuth2 | |
| // packages. | |
| func NewClient(ctx context.Context, src TokenSource) *http.Client { | |
| if src == nil { | |
| return internal.ContextClient(ctx) | |
| } | |
| return &http.Client{ | |
| Transport: &Transport{ | |
| Base: internal.ContextClient(ctx).Transport, | |
| Source: ReuseTokenSource(nil, src), | |
| }, | |
| } | |
| } | |
| // ReuseTokenSource returns a TokenSource which repeatedly returns the | |
| // same token as long as it's valid, starting with t. | |
| // When its cached token is invalid, a new token is obtained from src. | |
| // | |
| // ReuseTokenSource is typically used to reuse tokens from a cache | |
| // (such as a file on disk) between runs of a program, rather than | |
| // obtaining new tokens unnecessarily. | |
| // | |
| // The initial token t may be nil, in which case the TokenSource is | |
| // wrapped in a caching version if it isn't one already. This also | |
| // means it's always safe to wrap ReuseTokenSource around any other | |
| // TokenSource without adverse effects. | |
| func ReuseTokenSource(t *Token, src TokenSource) TokenSource { | |
| // Don't wrap a reuseTokenSource in itself. That would work, | |
| // but cause an unnecessary number of mutex operations. | |
| // Just build the equivalent one. | |
| if rt, ok := src.(*reuseTokenSource); ok { | |
| if t == nil { | |
| // Just use it directly. | |
| return rt | |
| } | |
| src = rt.new | |
| } | |
| return &reuseTokenSource{ | |
| t: t, | |
| new: src, | |
| } | |
| } | |
| // ReuseTokenSource returns a TokenSource that acts in the same manner as the | |
| // TokenSource returned by ReuseTokenSource, except the expiry buffer is | |
| // configurable. The expiration time of a token is calculated as | |
| // t.Expiry.Add(-earlyExpiry). | |
| func ReuseTokenSourceWithExpiry(t *Token, src TokenSource, earlyExpiry time.Duration) TokenSource { | |
| // Don't wrap a reuseTokenSource in itself. That would work, | |
| // but cause an unnecessary number of mutex operations. | |
| // Just build the equivalent one. | |
| if rt, ok := src.(*reuseTokenSource); ok { | |
| if t == nil { | |
| // Just use it directly, but set the expiryDelta to earlyExpiry, | |
| // so the behavior matches what the user expects. | |
| rt.expiryDelta = earlyExpiry | |
| return rt | |
| } | |
| src = rt.new | |
| } | |
| if t != nil { | |
| t.expiryDelta = earlyExpiry | |
| } | |
| return &reuseTokenSource{ | |
| t: t, | |
| new: src, | |
| expiryDelta: earlyExpiry, | |
| } | |
| } |