func (c *Client) Verify(ctx context.Context, authzEndpoint, enteredHost, code, verifier string) (me *url.URL, _ error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, authzEndpoint, strings.NewReader(url.Values{
		"grant_type":    {"authorization_code"},
		"code":          {code},
		"client_id":     {c.ClientID},
		"redirect_uri":  {c.RedirectURL},
		"code_verifier": {verifier},
	}.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("authorization endpoint verification request failed: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
		if ct := resp.Header.Get("Content-Type"); ct != "application/json" {
			log.Printf("indieauth.Client.Verify: got error response with non-'application/json' Content-Type header %q\n", ct)
			if mediaType, _, err := mime.ParseMediaType(ct); err != nil {
				return nil, fmt.Errorf("authorization endpoint returned bad Content-Type header %q: %v", ct, err)
			} else if mediaType != "application/json" {
				return nil, fmt.Errorf("authorization endpoint returned media type %q, want %q", mediaType, "application/json")
			}
		}
		var v struct{ Error string }
		err = json.NewDecoder(resp.Body).Decode(&v)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(v.Error)
	} else if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("authorization endpoint returned non-200/400/401 status code: %v", resp.Status)
	}
	if ct := resp.Header.Get("Content-Type"); ct != "application/json" {
		log.Printf("indieauth.Client.Verify: got successful response with non-'application/json' Content-Type header %q\n", ct)
		if mediaType, _, err := mime.ParseMediaType(ct); err != nil {
			return nil, fmt.Errorf("authorization endpoint returned bad Content-Type header %q: %v", ct, err)
		} else if mediaType != "application/json" {
			return nil, fmt.Errorf("authorization endpoint returned media type %q, want %q", mediaType, "application/json")
		}
	}
	var v struct{ Me string }
	err = json.NewDecoder(resp.Body).Decode(&v)
	if err != nil {
		return nil, fmt.Errorf("authorization endpoint returned invalid JSON: %v", err)
	}
	me, err = ParseUserProfile(v.Me)
	if err != nil {
		return nil, fmt.Errorf("authorization endpoint returned a bad user profile URL %q: %v", v.Me, err)
	}
	if me.Host != enteredHost {
		return nil, fmt.Errorf("authorization endpoint authenticated you as %q, doesn't match entered host %q", v.Me, enteredHost)
	}
	return me, nil
}
Verify makes a POST request to the authorization endpoint to verify
the authorization code and retrieve the final user profile URL.

An error is returned if the final user profile URL has a host that
does not equal enteredHost, the host of the entered user profile URL.

See https://indieauth.spec.indieweb.org/#authorization-code-verification
and https://indieauth.spec.indieweb.org/#differing-user-profile-urls.
package indieauth
import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"mime"
	"net/http"
	"net/url"
	"strings"
)
type Client struct {
	ClientID string
	RedirectURL string
}
func (c *Client) AuthnReqURL(authzEndpoint *url.URL, me, state, verifier string) string {
	return authzEndpoint.ResolveReference(&url.URL{
		RawQuery: url.Values{
			"me":                    {me},
			"client_id":             {c.ClientID},
			"redirect_uri":          {c.RedirectURL},
			"state":                 {state},
			"response_type":         {"code"},
			"code_challenge":        {s256Challenge(verifier)},
			"code_challenge_method": {"S256"},
		}.Encode(),
	}).String()
}
func s256Challenge(verifier string) string {
	s := sha256.Sum256([]byte(verifier))
	return base64.RawURLEncoding.EncodeToString(s[:])
}