diff --git a/internal/cli/get_token.go b/internal/cli/get_token.go index e64ae82e4..8bc2df219 100644 --- a/internal/cli/get_token.go +++ b/internal/cli/get_token.go @@ -40,9 +40,19 @@ Fetch an access token for the given client and API. return err } - // TODO: We can check here if the client is an m2m client, and if so + // We can check here if the client is an m2m client, and if so // initiate the client credentials flow instead to fetch a token, // avoiding the browser and HTTP server shenanigans altogether. + if client.GetAppType() == "non_interactive" { + tokenResponse, err := runClientCredentialsFlow(cli, client, clientID, audience) + if err != nil { + return err + } + + fmt.Fprint(cli.renderer.MessageWriter, "\n") + cli.renderer.GetToken(client, tokenResponse) + return nil + } if proceed := runLoginFlowPreflightChecks(cli, client); !proceed { return nil diff --git a/internal/cli/utils_shared.go b/internal/cli/utils_shared.go index 0a58420dc..7dff49342 100644 --- a/internal/cli/utils_shared.go +++ b/internal/cli/utils_shared.go @@ -3,12 +3,15 @@ package cli import ( "fmt" + "encoding/json" "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" + "strings" ) const ( @@ -23,6 +26,52 @@ var ( cliLoginTestingScopes []string = []string{"openid", "profile"} ) +// runClientCredentialsFlow runs an M2M client credentials flow without opening a browser +func runClientCredentialsFlow(cli *cli, c *management.Client, clientID string, audience string) (*authutil.TokenResponse, error) { + + var tokenResponse *authutil.TokenResponse + + tenant, err := cli.getTenant() + if err != nil { + return tokenResponse, err + } + + url := "https://" + tenant.Domain + "/oauth/token" + + cli.renderer.Infof("Domain: " + tenant.Domain) + cli.renderer.Infof("ClientID: " + clientID) + cli.renderer.Infof("Type: Machine to Machine") + fmt.Println() + + client_secret := c.GetClientSecret() + + // TODO: Check if the audience is valid, and suggest a different client if it is wrong. + + payload := strings.NewReader("grant_type=client_credentials&client_id=" + clientID + "&client_secret=" + client_secret + "&audience=" + audience) + + err = ansi.Spinner("Waiting for token", func() error { + req, _ := http.NewRequest("POST", url, payload) + + req.Header.Add("content-type", "application/x-www-form-urlencoded") + + res, err := http.DefaultClient.Do(req) + + if err != nil { + return err + } + + defer res.Body.Close() + + err = json.NewDecoder(res.Body).Decode(&tokenResponse) + if err != nil { + return fmt.Errorf("cannot decode response: %w", err) + } + return nil + }) + + return tokenResponse, err +} + // runLoginFlowPreflightChecks checks if we need to make any updates to the // client being tested in order to log in successfully. If so, it asks the user // to confirm whether to proceed.