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

internal: optionally skip urlencoding client id and secret in header #708

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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