Skip to content

Commit

Permalink
optionally skip urlencoding client credentials in basic authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
bachmanity1 committed Mar 1, 2024
1 parent 95bec95 commit 3c4ec80
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 5 deletions.
53 changes: 53 additions & 0 deletions clientcredentials/clientcredentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,56 @@ func TestTokenRefreshRequest(t *testing.T) {
c := conf.Client(context.Background())
c.Get(ts.URL + "/somethingelse")
}

func TestTokenRequestWithoutCredentialsEncoding(t *testing.T) {
attempt := 1
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if attempt != 3 {
w.WriteHeader(401)
attempt++
return
}
if r.URL.String() != "/token" {
t.Errorf("authenticate client request URL = %q; want %q", r.URL, "/token")
}
headerAuth := r.Header.Get("Authorization")
if headerAuth != "Basic Q0xJRU5UX0lEISs6Q0xJRU5UX1NFQ1JFVCYrQA==" {
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
}
if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
t.Errorf("Content-Type header = %q; want %q", got, want)
}
body, err := io.ReadAll(r.Body)
if err != nil {
r.Body.Close()
}
if err != nil {
t.Errorf("failed reading request body: %s.", err)
}
if string(body) != "grant_type=client_credentials&scope=scope" {
t.Errorf("payload = %q; want %q", string(body), "grant_type=client_credentials&scope=scope")
}
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&token_type=bearer"))
}))
defer ts.Close()
conf := &Config{
ClientID: "CLIENT_ID!+",
ClientSecret: "CLIENT_SECRET&+@",
Scopes: []string{"scope"},
TokenURL: ts.URL + "/token",
}
tok, err := conf.Token(context.Background())
if err != nil {
t.Error(err)
}
if !tok.Valid() {
t.Fatalf("token invalid. got: %#v", tok)
}
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
t.Errorf("Access token = %q; want %q", tok.AccessToken, "90d64460d14870c08c81352a05dedd3465940a7c")
}
if tok.TokenType != "bearer" {
t.Errorf("token type = %q; want %q", tok.TokenType, "bearer")
}
}
21 changes: 16 additions & 5 deletions internal/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"mime"
"net/http"
Expand Down Expand Up @@ -111,9 +110,10 @@ func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
type AuthStyle int

const (
AuthStyleUnknown AuthStyle = 0
AuthStyleInParams AuthStyle = 1
AuthStyleInHeader AuthStyle = 2
AuthStyleUnknown AuthStyle = 0
AuthStyleInParams AuthStyle = 1
AuthStyleInHeader AuthStyle = 2
AuthStyleInHeaderWithoutEncoding AuthStyle = 3
)

// LazyAuthStyleCache is a backwards compatibility compromise to let Configs
Expand Down Expand Up @@ -197,6 +197,8 @@ func newTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, auth
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if authStyle == AuthStyleInHeader {
req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
} else if authStyle == AuthStyleInHeaderWithoutEncoding {
req.SetBasicAuth(clientID, clientSecret)
}
return req, nil
}
Expand Down Expand Up @@ -240,6 +242,15 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
authStyle = AuthStyleInParams // the second way we'll try
req, _ = newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
token, err = doTokenRoundTrip(ctx, req)

// although https://tools.ietf.org/html/rfc6749#section-2.3.1 states that
// clientID & clientSecret must be urlencoded in the authorization header
// there are some sites that don't do this thus resulting in authentication failure
if err != nil {
authStyle = AuthStyleInHeaderWithoutEncoding
req, _ = newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
token, err = doTokenRoundTrip(ctx, req)
}
}
if needsAuthStyleProbe && err == nil {
styleCache.setAuthStyle(tokenURL, authStyle)
Expand All @@ -257,7 +268,7 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
r.Body.Close()
if err != nil {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
Expand Down
4 changes: 4 additions & 0 deletions oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ const (
// using HTTP Basic Authorization. This is an optional style
// described in the OAuth2 RFC 6749 section 2.3.1.
AuthStyleInHeader AuthStyle = 2

// AuthStyleInHeaderWithoutEncoding is similar to the AuthStyleInHeader,
// but it doesn't url encode client_id and client_password
AuthStyleInHeaderWithoutEncoding AuthStyle = 3
)

var (
Expand Down

0 comments on commit 3c4ec80

Please sign in to comment.