Fixing go-oauth2’s case sensitive bearer token authorization headers
The Go OAuth2 package does not always comply with the OAuth 2.0 specification in regards to case sensitivity in authorization headers using bearer tokens. It can cause incompatibilities with strict OAuth 2.0 providers. This article presents a workaround, without requiring any changes to the go-oauth2 code itself.
Problem statement
As per RFC 6749 section 5.1
the token_type
literal returned when requesting access or refresh tokens
is case insensitive:
token_type: Required. The type of the token issued as described in Section 7.1. Value is case insensitive.
The token type determines the authorization scheme used for requesting access and refresh tokens. The most common token type is the bearer token. Usually the provider describes it as “Bearer”, but some providers will return “bearer” (lowercase) instead.
On the other hand, RFC 6750 section 2.1 states that the Authorization header scheme for bearer tokens must be capitalized:
Clients should make authenticated requests with a bearer token using the “Authorization” request header field with the “Bearer” HTTP authorization scheme.
For example:
GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer mF_9.B5f-4.1JqM
The syntax for Bearer credentials is as follows:
b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"=" credentials = "Bearer" 1*SP b64token
Yet go-oauth2 stores the token_type
as returned by the provider and uses it
verbatim when requesting access and refresh tokens. This causes problems when a
provider replies with a non-capitalized “bearer” token type but expects a
capitalized “Bearer” scheme for authentication. Such a provider still adheres to
the specification but go-oauth2 cannot interact with it.
The Amazon Cloud Drive
API does
exactly that: it returns a lowercase token_type
but expects a capitalized
scheme name in authorization requests. I’ve ran into this issue while working on
acdcli and received HTTP 403: Forbidden
errors when refreshing the token.
Solution
While researching the problem I came across an open issue on github and two solutions. Both solutions require modifying the go-oauth2 source code, which I want to avoid.
Instead I’ve created a transport wrapper for the HTTP client. When it encounters
a request with an Authorization header of scheme bearer
, it will replace it
with a cloned request of authorization scheme Bearer
and pass it on to the
wrapped transport. This complies with the OAuth 2.0 specification and fixes the
issue.
package client
import (
"net/http"
"strings"
)
// BearerAuthTransport wraps a RoundTripper. It capitalized bearer token
// authorization headers.
type BearerAuthTransport struct {
rt http.RoundTripper
}
// RoundTrip satisfies the RoundTripper interface. It replaces authorization
// headers of scheme `bearer` by capitalized `Bearer` (as per OAuth 2.0 spec).
func (t *BearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
auth := req.Header.Get("Authorization")
if strings.HasPrefix(strings.ToLower(auth), "bearer ") {
auth = "Bearer " + auth[7:]
}
req2 := cloneRequest(req) // per RoundTripper contract
req2.Header.Set("Authorization", auth)
return t.rt.RoundTrip(req2)
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}
The transport wrapper BearerAuthTransport
is used as follows:
ctx := context.Background()
// Prepare wrapper to fix Bearer authorization
var transport http.RoundTripper = &BearerAuthTransport{http.DefaultTransport}
// Override default HTTP client in ctx
ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Transport: transport})
config := ... // the oauth2 config
token := ... // your favorite way to get the token, or nil
client := config.Client(ctx, token)
Out in the wild, the transport can be found here and its usage here.
This solution will continue to work even when (or if) go-oauth2 gets fixed. It won’t conflict in any way.
Comments
Comments were disabled in March 2022. Since this page was created earlier, there may have been previous comments which are now inaccessible. Sorry.