Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Auth0 PKCE flow for a CLI built in golang
package auth
import (
cv ""
// AuthorizeUser implements the PKCE OAuth2 flow.
func AuthorizeUser(clientID string, authDomain string, redirectURL string) {
// initialize the code verifier
var CodeVerifier, _ = cv.CreateCodeVerifier()
// Create code_challenge with S256 method
codeChallenge := CodeVerifier.CodeChallengeS256()
// construct the authorization URL (with Auth0 as the authorization provider)
authorizationURL := fmt.Sprintf(
authDomain, clientID, codeChallenge, redirectURL)
// start a web server to listen on a callback URL
server := &http.Server{Addr: redirectURL}
// define a handler that will get the authorization code, call the token endpoint, and close the HTTP server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// get the authorization code
code := r.URL.Query().Get("code")
if code == "" {
fmt.Println("snap: Url Param 'code' is missing")
io.WriteString(w, "Error: could not find 'code' URL parameter\n")
// close the HTTP server and return
// trade the authorization code and the code verifier for an access token
codeVerifier := CodeVerifier.String()
token, err := getAccessToken(clientID, codeVerifier, code, redirectURL)
if err != nil {
fmt.Println("snap: could not get access token")
io.WriteString(w, "Error: could not retrieve access token\n")
// close the HTTP server and return
viper.Set("AccessToken", token)
err = viper.WriteConfig()
//_, err = config.WriteConfigFile("auth.json", token)
if err != nil {
fmt.Println("snap: could not write config file")
io.WriteString(w, "Error: could not store access token\n")
// close the HTTP server and return
// return an indication of success to the caller
io.WriteString(w, `
<h1>Login successful!</h1>
<h2>You can close this window and return to the snap CLI.</h2>
fmt.Println("Successfully logged into snapmaster API.")
// close the HTTP server
// parse the redirect URL for the port number
u, err := url.Parse(redirectURL)
if err != nil {
fmt.Printf("snap: bad redirect URL: %s\n", err)
// set up a listener on the redirect port
port := fmt.Sprintf(":%s", u.Port())
l, err := net.Listen("tcp", port)
if err != nil {
fmt.Printf("snap: can't listen to port %s: %s\n", port, err)
// open a browser window to the authorizationURL
err = open.Start(authorizationURL)
if err != nil {
fmt.Printf("snap: can't open browser to URL %s: %s\n", authorizationURL, err)
// start the blocking web server loop
// this will exit when the handler gets fired and calls server.Close()
// getAccessToken trades the authorization code retrieved from the first OAuth2 leg for an access token
func getAccessToken(clientID string, codeVerifier string, authorizationCode string, callbackURL string) (string, error) {
// set the url and form-encoded data for the POST to the access token endpoint
url := ""
data := fmt.Sprintf(
clientID, codeVerifier, authorizationCode, callbackURL)
payload := strings.NewReader(data)
// create the request and execute it
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 {
fmt.Printf("snap: HTTP error: %s", err)
return "", err
// process the response
defer res.Body.Close()
var responseData map[string]interface{}
body, _ := ioutil.ReadAll(res.Body)
// unmarshal the json into a string map
err = json.Unmarshal(body, &responseData)
if err != nil {
fmt.Printf("snap: JSON error: %s", err)
return "", err
// retrieve the access token out of the map, and return to caller
accessToken := responseData["access_token"].(string)
return accessToken, nil
// cleanup closes the HTTP server
func cleanup(server *http.Server) {
// we run this as a goroutine so that this function falls through and
// the socket to the browser gets flushed/closed before the server goes away
go server.Close()
Copy link

cadethacker commented Aug 25, 2020

You are a scholar and a gentleman. You just saved me hours of hacking around.

Copy link

cadethacker commented Aug 25, 2020

In looking at the google code, I see that they try a bunch of ports until they find one. That means they setup all those as valid redirects. They did 100, but might be worth adding 10 valid redirects. Anybody using a CLI is probably also running a bunch of test code :D

port := 8000
	foundOpenPort := false

	for port < 8010 {

		host := fmt.Sprintf("localhost:%d", port)

		fmt.Printf("Trying %s", host)
		ln, err := net.Listen("tcp", host)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can't listen on port %d: %s", port, err)
			// move to next port
			port = port + 1

		_ = ln.Close()
		foundOpenPort = true

	fmt.Printf("TCP Port %d is available\n", port)

	redirectURL := fmt.Sprintf("http://localhost:%d/identity/callback", port)

	if !foundOpenPort {
		err := fmt.Errorf("Unable to find an open port, failing")
		return err

I found this somewhere, and added it near the top. Just a passing gift.

Copy link

ogazitt commented Aug 26, 2020

Thanks @cadethacker! Glad it was helpful. Also, good find on the code that finds an open port - that's a great practical addition to the gist.

Copy link

jimlambrt commented Dec 20, 2020

FYI: uses math/rand to generate a non-cryptographic random string for the verifier which means it has a timing vulnerability.

I've opened a PR to address it: nirasan/go-oauth-pkce-code-verifier#1

Until the PR is merged, I would suggest not using that package for PKCE implementations.

Copy link

oogali commented Sep 13, 2022

The pull request from @jimlambrt was merged on May 9, 2022.

I believe this should be safe[r] to use now...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment