diff --git a/example_test.go b/example_test.go index c1e0bba..de8e9f8 100644 --- a/example_test.go +++ b/example_test.go @@ -28,7 +28,7 @@ func Example_config() { // Redirect user to consent page to ask for permission // for the scopes specified above. - url := conf.AuthCodeURL("") + url := conf.AuthCodeURL("state", "online", "auto") fmt.Printf("Visit the URL for the auth dialog: %v", url) // Use the authorization code that is pushed to the redirect URL. diff --git a/google/example_test.go b/google/example_test.go index 3aba789..40f8bd9 100644 --- a/google/example_test.go +++ b/google/example_test.go @@ -36,7 +36,7 @@ func Example_webServer() { // Redirect user to Google's consent page to ask for permission // for the scopes specified above. - url := config.AuthCodeURL("") + url := config.AuthCodeURL("state", "online", "auto") fmt.Printf("Visit the URL for the auth dialog: %v", url) // Handle the exchange code to initiate a transport diff --git a/oauth2.go b/oauth2.go index dd3a7f8..739e73b 100644 --- a/oauth2.go +++ b/oauth2.go @@ -10,6 +10,7 @@ package oauth2 import ( "encoding/json" "errors" + "fmt" "io/ioutil" "mime" "net/http" @@ -39,15 +40,6 @@ type TokenFetcher interface { // Options represents options to provide OAuth 2.0 client credentials // and access level. A sample configuration: -// -// opts := &oauth2.Options{ -// ClientID: "", -// ClientSecret: "ad4364309eff", -// RedirectURL: "https://homepage/oauth2callback", -// Scopes: []string{"scope1", "scope2"}, -// AccessType: "offline", // retrieves a refresh token -// } -// type Options struct { // ClientID is the OAuth client identifier used when communicating with // the configured OAuth provider. @@ -63,26 +55,6 @@ type Options struct { // Scopes optionally specifies a list of requested permission scopes. Scopes []string `json:"scopes,omitempty"` - - // AccessType is an OAuth extension that gets sent as the - // "access_type" field in the URL from AuthCodeURL. - // See https://developers.google.com/accounts/docs/OAuth2WebServer. - // It may be "online" (the default) or "offline". - // 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. - AccessType string `json:"access_type,omitempty"` - - // ApprovalPrompt indicates whether the user should be - // re-prompted for consent. If set to "auto" (default) the - // user will be prompted only if they haven't previously - // granted consent and the code can only be exchanged for an - // access token. - // If set to "force" the user will always be prompted, and the - // code can be exchanged for a refresh token. - ApprovalPrompt string `json:"-"` } // NewConfig creates a generic OAuth 2.0 configuration that talks @@ -127,7 +99,28 @@ type Config struct { // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page // that asks for permissions for the required scopes explicitly. -func (c *Config) AuthCodeURL(state string) (authURL string) { +// +// State is a token to protect the user from CSRF attacks. You must +// always provide a non-zero string and validate that it matches the +// the state query parameter on your redirect callback. +// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. +// +// Access type is an OAuth extension that gets sent as the +// "access_type" field in the URL from AuthCodeURL. +// It may be "online" (default) or "offline". +// 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. +// +// Approval prompt indicates whether the user should be +// re-prompted for consent. If set to "auto" (default) the +// user will be prompted only if they haven't previously +// granted consent and the code can only be exchanged for an +// access token. If set to "force" the user will always be prompted, +// and the code can be exchanged for a refresh token. +func (c *Config) AuthCodeURL(state, accessType, prompt string) (authURL string) { u := *c.authURL v := url.Values{ "response_type": {"code"}, @@ -135,8 +128,8 @@ func (c *Config) AuthCodeURL(state string) (authURL string) { "redirect_uri": condVal(c.opts.RedirectURL), "scope": condVal(strings.Join(c.opts.Scopes, " ")), "state": condVal(state), - "access_type": condVal(c.opts.AccessType), - "approval_prompt": condVal(c.opts.ApprovalPrompt), + "access_type": condVal(accessType), + "approval_prompt": condVal(prompt), } q := v.Encode() if u.RawQuery == "" { @@ -210,9 +203,12 @@ func (c *Config) retrieveToken(v url.Values) (*Token, error) { return nil, err } defer r.Body.Close() - if r.StatusCode != 200 { - // TODO(jbd): Add status code or error message - return nil, errors.New("oauth2: can't retrieve a new token") + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + if c := r.StatusCode; c < 200 || c > 299 { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) } resp := &tokenRespBody{} diff --git a/oauth2_test.go b/oauth2_test.go index 7882dc9..c760605 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -29,15 +29,13 @@ func newTestConf(url string) *Config { "scope1", "scope2", }, - AccessType: "offline", - ApprovalPrompt: "force", }, url+"/auth", url+"/token") return conf } func TestAuthCodeURL(t *testing.T) { conf := newTestConf("server") - url := conf.AuthCodeURL("foo") + url := conf.AuthCodeURL("foo", "offline", "force") if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" { t.Fatalf("Auth code URL doesn't match the expected, found: %v", url) } @@ -47,7 +45,7 @@ func TestAuthCodeURL_Optional(t *testing.T) { conf, _ := NewConfig(&Options{ ClientID: "CLIENT_ID", }, "auth-url", "token-url") - url := conf.AuthCodeURL("") + url := conf.AuthCodeURL("", "", "") if url != "auth-url?client_id=CLIENT_ID&response_type=code" { t.Fatalf("Auth code URL doesn't match the expected, found: %v", url) } diff --git a/transport.go b/transport.go index e1367e7..706947b 100644 --- a/transport.go +++ b/transport.go @@ -36,7 +36,7 @@ type Token struct { // initialized as needed. Extra map[string]string `json:"extra,omitempty"` - // JWT related fields + // Subject is the user to impersonate. Subject string `json:"subject,omitempty"` }