diff --git a/internal/auth/authutil/browser.go b/internal/auth/authutil/browser.go index afed22280..093640f29 100644 --- a/internal/auth/authutil/browser.go +++ b/internal/auth/authutil/browser.go @@ -10,9 +10,10 @@ import ( // WaitForBrowserCallback lauches a new HTTP server listening on the provided // address and waits for a request. Once received, the code is extracted from // the query string (if any), and returned it to the caller. -func WaitForBrowserCallback(addr string) (string, error) { +func WaitForBrowserCallback(addr string) (code string, state string, err error) { type callback struct { code string + state string err string errDescription string } @@ -26,6 +27,7 @@ func WaitForBrowserCallback(addr string) (string, error) { m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { cb := &callback{ code: r.URL.Query().Get("code"), + state: r.URL.Query().Get("state"), err: r.URL.Query().Get("error"), errDescription: r.URL.Query().Get("error_description"), } @@ -55,8 +57,8 @@ func WaitForBrowserCallback(addr string) (string, error) { if cb.err != "" { err = fmt.Errorf("%s: %s", cb.err, cb.errDescription) } - return cb.code, err + return cb.code, cb.state, err case err := <-errCh: - return "", err + return "", "", err } } diff --git a/internal/auth/authutil/login.go b/internal/auth/authutil/login.go index 5df350d23..e146be2eb 100644 --- a/internal/auth/authutil/login.go +++ b/internal/auth/authutil/login.go @@ -7,13 +7,14 @@ import ( // BuildLoginURL constructs a URL + query string that can be used to // initiate a user-facing login-flow from the CLI. -func BuildLoginURL(domain, clientID, callbackURL, connectionName, audience, prompt string, scopes []string) (string, error) { +func BuildLoginURL(domain, clientID, callbackURL, state, connectionName, audience, prompt string, scopes []string) (string, error) { var path string = "/authorize" q := url.Values{} q.Add("client_id", clientID) q.Add("response_type", "code") q.Add("redirect_uri", callbackURL) + q.Add("state", state) if prompt != "" { q.Add("prompt", prompt) diff --git a/internal/cli/utils_shared.go b/internal/cli/utils_shared.go index caa7d2ab4..d2cbe63e2 100644 --- a/internal/cli/utils_shared.go +++ b/internal/cli/utils_shared.go @@ -1,17 +1,20 @@ package cli import ( + "crypto/rand" "fmt" + "encoding/base64" "encoding/json" + "net/http" + "net/url" + "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth/authutil" "github.com/auth0/auth0-cli/internal/auth0" "github.com/auth0/auth0-cli/internal/open" "github.com/auth0/auth0-cli/internal/prompt" "gopkg.in/auth0.v5/management" - "net/http" - "net/url" ) const ( @@ -20,6 +23,7 @@ const ( cliLoginTestingCallbackAddr string = "localhost:8484" cliLoginTestingCallbackURL string = "http://localhost:8484" cliLoginTestingInitiateLoginURI string = "https://cli.auth0.com" + cliLoginTestingStateSize int = 64 ) var ( @@ -111,8 +115,13 @@ func runLoginFlow(cli *cli, t tenant, c *management.Client, connName, audience, return err } + state, err := generateState(cliLoginTestingStateSize) + if err != nil { + return err + } + // Build a login URL and initiate login in a browser window. - loginURL, err := authutil.BuildLoginURL(t.Domain, c.GetClientID(), cliLoginTestingCallbackURL, connName, audience, prompt, scopes) + loginURL, err := authutil.BuildLoginURL(t.Domain, c.GetClientID(), cliLoginTestingCallbackURL, state, connName, audience, prompt, scopes) if err != nil { return err } @@ -123,11 +132,15 @@ func runLoginFlow(cli *cli, t tenant, c *management.Client, connName, audience, // launch a HTTP server to wait for the callback to capture the auth // code. - authCode, err := authutil.WaitForBrowserCallback(cliLoginTestingCallbackAddr) + authCode, authState, err := authutil.WaitForBrowserCallback(cliLoginTestingCallbackAddr) if err != nil { return err } + if state != authState { + return fmt.Errorf("unexpected auth state") + } + // once the callback is received, exchange the code for an access // token. tokenResponse, err = authutil.ExchangeCodeForToken( @@ -235,3 +248,15 @@ func removeLocalCallbackURLFromClient(clientManager auth0.ClientAPI, client *man return clientManager.Update(client.GetClientID(), updatedClient) } + +// generate state parameter value used to mitigate CSRF attacks +// more: https://auth0.com/docs/protocols/state-parameters +func generateState(size int) (string, error) { + b := make([]byte, size) + _, err := rand.Read(b) + if err != nil { + return "", err + } + + return base64.URLEncoding.EncodeToString(b), nil +}