We have an oauth2 endpoint that seems to require a client_credentials token to use as a Bearer for the initial code to token exchange process. I have successfully obtained the token for that.
However, in go's current implementation of oauth2 client lib, the Exchange() function (see: Exchange which eventually calls RetrieveToken)
It doesn't add an "Authentication: Bearer " header with a token I can slip in during the Exchange. It can however add a Basic auth header. Our implementation does not currently support basic auth though.
If possible, I'd like to make it add the header without modifying the source code to to oauth2 package.
It would appear if I call oauth2.RegisterBrokenAuthHeaderProvider it might skip the attempt to add a basic auth header which is needed in my case since that'll not work.
Then finally the call to do the http request which is setup as a POST.
Code:
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
hc, err := ContextClient(ctx)
if err != nil {
return nil, err
}
bustedAuth := !providerAuthHeaderWorks(tokenURL)
if bustedAuth {
if clientID != "" {
v.Set("client_id", clientID)
}
if clientSecret != "" {
v.Set("client_secret", clientSecret)
}
}
req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth {
req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
}
r, err := ctxhttp.Do(ctx, hc, req)
if err != nil {
return nil, err
}
// rest of code ...
}
the ctxhttp.Do can take a context. I've been reading about these but I don't understand how they work. Can I use them to add my header or any other headers I might require?
I'm not sure if this is the most optimal way of doing it, but it seems to work. This is the redirect callback handler that is called as a result of visiting the an auth endpoint.
// Create a custom tokenSource type
type tokenSource struct{ token *oauth2.Token }
func (t *tokenSource) Token() (*oauth2.Token, error) {
fmt.Println("TokenSource!", t.token)
return t.token, nil
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'
", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
code := r.FormValue("code")
// Create a tokenSource and fill in our application Bearer token
ts := &tokenSource{
token: &oauth2.Token{
AccessToken: appToken.AccessToken,
TokenType: appToken.TokenType,
},
}
tr := &oauth2.Transport{
Source: ts,
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{
Transport: tr,
})
// myConfig defined previously and in the usual way. Look at the examples.
token, err := myConfig.Exchange(ctx, code)
if err != nil {
fmt.Printf("Code exchange failed with '%s'
", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Printf("token: %+v", token)
// token can now be used.
}