Access type and approval prompt should be set at URL generation.

This commit is contained in:
Burcu Dogan 2014-09-04 22:43:23 -07:00
parent cb029f4c1f
commit 97a89b3be5
5 changed files with 36 additions and 42 deletions

View File

@ -28,7 +28,7 @@ func Example_config() {
// Redirect user to consent page to ask for permission // Redirect user to consent page to ask for permission
// for the scopes specified above. // 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) fmt.Printf("Visit the URL for the auth dialog: %v", url)
// Use the authorization code that is pushed to the redirect URL. // Use the authorization code that is pushed to the redirect URL.

View File

@ -36,7 +36,7 @@ func Example_webServer() {
// Redirect user to Google's consent page to ask for permission // Redirect user to Google's consent page to ask for permission
// for the scopes specified above. // 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) fmt.Printf("Visit the URL for the auth dialog: %v", url)
// Handle the exchange code to initiate a transport // Handle the exchange code to initiate a transport

View File

@ -10,6 +10,7 @@ package oauth2
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"mime" "mime"
"net/http" "net/http"
@ -39,15 +40,6 @@ type TokenFetcher interface {
// Options represents options to provide OAuth 2.0 client credentials // Options represents options to provide OAuth 2.0 client credentials
// and access level. A sample configuration: // and access level. A sample configuration:
//
// opts := &oauth2.Options{
// ClientID: "<clientID>",
// ClientSecret: "ad4364309eff",
// RedirectURL: "https://homepage/oauth2callback",
// Scopes: []string{"scope1", "scope2"},
// AccessType: "offline", // retrieves a refresh token
// }
//
type Options struct { type Options struct {
// ClientID is the OAuth client identifier used when communicating with // ClientID is the OAuth client identifier used when communicating with
// the configured OAuth provider. // the configured OAuth provider.
@ -63,26 +55,6 @@ type Options struct {
// Scopes optionally specifies a list of requested permission scopes. // Scopes optionally specifies a list of requested permission scopes.
Scopes []string `json:"scopes,omitempty"` 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 // 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 // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
// that asks for permissions for the required scopes explicitly. // 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 u := *c.authURL
v := url.Values{ v := url.Values{
"response_type": {"code"}, "response_type": {"code"},
@ -135,8 +128,8 @@ func (c *Config) AuthCodeURL(state string) (authURL string) {
"redirect_uri": condVal(c.opts.RedirectURL), "redirect_uri": condVal(c.opts.RedirectURL),
"scope": condVal(strings.Join(c.opts.Scopes, " ")), "scope": condVal(strings.Join(c.opts.Scopes, " ")),
"state": condVal(state), "state": condVal(state),
"access_type": condVal(c.opts.AccessType), "access_type": condVal(accessType),
"approval_prompt": condVal(c.opts.ApprovalPrompt), "approval_prompt": condVal(prompt),
} }
q := v.Encode() q := v.Encode()
if u.RawQuery == "" { if u.RawQuery == "" {
@ -210,9 +203,12 @@ func (c *Config) retrieveToken(v url.Values) (*Token, error) {
return nil, err return nil, err
} }
defer r.Body.Close() defer r.Body.Close()
if r.StatusCode != 200 { body, err := ioutil.ReadAll(r.Body)
// TODO(jbd): Add status code or error message if err != nil {
return nil, errors.New("oauth2: can't retrieve a new token") 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{} resp := &tokenRespBody{}

View File

@ -29,15 +29,13 @@ func newTestConf(url string) *Config {
"scope1", "scope1",
"scope2", "scope2",
}, },
AccessType: "offline",
ApprovalPrompt: "force",
}, url+"/auth", url+"/token") }, url+"/auth", url+"/token")
return conf return conf
} }
func TestAuthCodeURL(t *testing.T) { func TestAuthCodeURL(t *testing.T) {
conf := newTestConf("server") 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" { 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) 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{ conf, _ := NewConfig(&Options{
ClientID: "CLIENT_ID", ClientID: "CLIENT_ID",
}, "auth-url", "token-url") }, "auth-url", "token-url")
url := conf.AuthCodeURL("") url := conf.AuthCodeURL("", "", "")
if url != "auth-url?client_id=CLIENT_ID&response_type=code" { if url != "auth-url?client_id=CLIENT_ID&response_type=code" {
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url) t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
} }

View File

@ -36,7 +36,7 @@ type Token struct {
// initialized as needed. // initialized as needed.
Extra map[string]string `json:"extra,omitempty"` Extra map[string]string `json:"extra,omitempty"`
// JWT related fields // Subject is the user to impersonate.
Subject string `json:"subject,omitempty"` Subject string `json:"subject,omitempty"`
} }