Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the get-tokens command #56

Merged
merged 6 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion internal/cli/get_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
Expand Down
44 changes: 44 additions & 0 deletions internal/cli/utils_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -23,6 +26,47 @@ 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, tenant tenant) (*authutil.TokenResponse, error) {

var tokenResponse *authutil.TokenResponse

url := "https://" + tenant.Domain + "/oauth/token"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use url.URL here, might be safer/cleaner than building the URL manually, there's an example at https://github.com/auth0/auth0-cli/blob/main/internal/auth/authutil/login.go#L34

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! I was feeling like there must be a better way!


cli.renderer.Infof("Domain: " + tenant.Domain)
cli.renderer.Infof("ClientID: " + clientID)
cli.renderer.Infof("Type: Machine to Machine")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be useful at the command-level, so it works for both types of flow, have you considered moving it up there?

fmt.Println()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will print to stdout which might mess with piping the result to another process (like jq), have you tried that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jq won't be confused by whitespace.

However, we could switch to cli.renderer.Infof("") if that would be prettier. I didn't use it because it would put the green triangle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 actually, it would be nice if auth0 get-token --format json would output only json. Do you know if the tool already has logic like this? Seeing an example of the tool hiding output would be great.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that's what this line is doing https://github.com/auth0/auth0-cli/pull/56/files#diff-32da9d2d97d78dec736cae68c2195111921918696567466ad99f78a291237298R52

fmt.Fprint(cli.renderer.MessageWriter, "\n") MessageWriter goes to stderr, while ResultWriter goes to stdout. That means we can print whatever we want to stderr but the only things that'll get piped onwards are results printed via the results writer.

A --quiet flag or similar might be a good addition though, avoid even writing the informational stuff to stderr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Okay that helps. Thanks Paddy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The STDOUT split seems to work

$ go run ./cmd/auth0 get-token --audience https://cli.test.auth0.fake  --client-id wgxFPH6hY3JRwYjAjiFUxU9sjxNheLOm --format json > /dev/null | jq '.access_token' | pbcopy

...token is on my clipboard!  ready to paste into Insomnia


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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use url.Values to avoid building up the payload string manually, it'll also take care of all sorts of encoding edge cases for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat, I've switched!


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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might http.PostForm be a little cleaner here/more succinct? there's an example at https://github.com/auth0/auth0-cli/blob/main/internal/auth/auth.go#L89-L98

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostForm?! What's this? Hello new friend.


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.
Expand Down