diff --git a/internal/cli/get_token.go b/internal/cli/get_token.go index e64ae82e4..e55aa05a2 100644 --- a/internal/cli/get_token.go +++ b/internal/cli/get_token.go @@ -40,9 +40,25 @@ 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 + appType := client.GetAppType() + + cli.renderer.Infof("Domain: " + tenant.Domain) + cli.renderer.Infof("ClientID: " + clientID) + cli.renderer.Infof("Type: " + appType + "\n") + + // 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 appType == "non_interactive" { + tokenResponse, err := runClientCredentialsFlow(cli, client, clientID, audience, tenant) + 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..caa7d2ab4 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" + "net/url" ) const ( @@ -23,6 +26,55 @@ var ( cliLoginTestingScopes []string = []string{"openid", "profile"} ) +func BuildOauthTokenURL(domain string) string { + var path string = "/oauth/token" + + u := &url.URL{ + Scheme: "https", + Host: domain, + Path: path, + } + + return u.String() +} + +func BuildOauthTokenParams(clientID, clientSecret, audience string) url.Values { + q := url.Values{ + "audience": {audience}, + "client_id": {clientID}, + "client_secret": {clientSecret}, + "grant_type": {"client_credentials"}, + } + return q +} + +// runClientCredentialsFlow runs an M2M client credentials flow without opening a browser +func runClientCredentialsFlow(cli *cli, c *management.Client, clientID string, audience string, tenant tenant) (*authutil.TokenResponse, error) { + + var tokenResponse *authutil.TokenResponse + + tokenURL := BuildOauthTokenURL(tenant.Domain) + payload := BuildOauthTokenParams(clientID, c.GetClientSecret(), audience) + + // TODO: Check if the audience is valid, and suggest a different client if it is wrong. + + err := ansi.Spinner("Waiting for token", func() error { + res, err := http.PostForm(tokenURL, payload) + 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.