daskeyboard / daskeyboard.io

Q - Das Keyboard Q documentation website for REST API and more.
https://www.daskeyboard.io
Apache License 2.0
77 stars 31 forks source link

OAuth2 Mime Type Incorrect #2

Open siennathesane opened 6 years ago

siennathesane commented 6 years ago

RFC 6749, which defines the OAuth2 framework, states OAuth endpoints must accept application/x-www-form-urlencoded and may return application/json. Currently, the endpoint only accepts application/json, which makes it non-compliant with the RFC and a lot of standard library clients across varying languages.

Currently the Q Cloud authentication API doesn't work in Go with RFC-compliant, standard library client due to the incorrect mime type being accepted:

func NewClient(cid, cis string) *FiveQClient {
        // golang.org/x/oauth2/clientcredentials
    tconf := clientcredentials.Config{ 
        ClientID:     cid,
        ClientSecret: cis,
        TokenURL:     TokenPath,
    }

    c := tconf.Client(context.Background())
    return &FiveQClient{c, &Me{}}
}

// GetAuthorizedClients returns a list of AuthorizedClients which have been allowed access to the 5Q software.
func (f *FiveQClient) GetAuthorizedClients() ([]AuthorizedClients, error) {
    var ldevs []AuthorizedClients
    resp, err := f.Get(AuthorisedClients)
    if err != nil {
        return []AuthorizedClients{}, err
    }
    defer resp.Body.Close()

    decoder := json.NewDecoder(resp.Body)
    err = decoder.Decode(&ldevs)
    if err != nil {
        return []AuthorizedClients{}, nil
    }

    return ldevs, nil
}

Returns this error:

oauth2: cannot fetch token: 400 
        Response: {"code":"BAD_REQUEST","message":"Missing parameter (client_id)."}

Changing the server mime type from application/json to application/x-www-form-urlencoded should resolve the problem.

jordanbtucker commented 6 years ago

Sorry for this long post, but I just want to help you get this implementation right.


In addition to supporting x-www-form-urlencoded POST bodies, OAuth implementations MUST also support HTTP Basic authentication.

According to the RFC:

Clients in possession of a client password MAY use the HTTP Basic authentication scheme as defined in [RFC2617] to authenticate with the authorization server. The client identifier is encoded using the "application/x-www-form-urlencoded" encoding algorithm per Appendix B, and the encoded value is used as the username; the client password is encoded using the same algorithm and used as the password. The authorization server MUST support the HTTP Basic authentication scheme for authenticating clients that were issued a client password.

First, note that clients MAY use HTTP Basic authentication, but authorization servers MUST support it.

Second, note that the client identifier (client_id) is encoded using the "application/x-www-form-urlencoded" encoding. This does not mean that the client_id must be sent in the HTTP POST body. It just defines how the client_id must be encoded when used with HTTP Basic authentication. The RFC clarifies this later.

Third, the client_id (that was encoded using x-www-form-urlencoded) is used as the username in HTTP Basic authentication. The client password (client_secret) is also encoded using x-www-form-urlencoded and used as the password in HTTP Basic authentication. Again, this is just referring to how the username and password are encoded, not how they are sent in the HTTP request.

The RFC goes on to say:

Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes). The parameters can only be transmitted in the request-body and MUST NOT be included in the request URI.

Note that the RFC recommends that client credentials are NOT sent in x-www-form-urlencoded POST bodies.


Here's an example:

The client_id is encoded using the x-www-form-urlencoded algorithm. The result is dCSQFR0i0mn3Gfd0o789OT%2B8.

The client_secret is also encoded using the x-www-form-urlencoded algorith. The result is AMr2mzyWCVIZF2wBkycimf%2Fk.

Note that the encoded client_id and client_secret contain %2B and %2F where there was a + and / respectively. If your implementation only generates client credentials that don't contain characters that need to be escaped by the x-www-form-urlencoded algorithm, then you can skip the step of encoding them.

Next, the encoded client_id and client_secret are further encoded using HTTP Basic authentication. To do this, take the client_id, append a : character, append the client_secret, then Base64 encode the whole result. In this example, that would be dCSQFR0i0mn3Gfd0o789OT%2B8:AMr2mzyWCVIZF2wBkycimf%2Fk, which is Base64 encoded to am9yZGFuYnR1Y2tlckBnbWFpbC5jb206TkZQOVkjR3VYcGR4b1NSUjhGKms=.

Finally, the HTTP header looks like this:

Authorization: Basic am9yZGFuYnR1Y2tlckBnbWFpbC5jb206TkZQOVkjR3VYcGR4b1NSUjhGKms=

Putting all of that together looks like this:

'Authorization: Basic ' + Base64(UrlEncode(client_id) + ':' + UrlEncode(client_secret))

You may conclude that, since the x-www-form-urlencoded algorithm only refers to the encoding of the client_id and client_secret before they are used with HTTP Basic authentication, there is no need to support x-www-form-urlencoded POST bodies.

However, OAuth implementations must still support x-www-form-urlencoded POST bodies for other parameters like grant_type or username or password. You can see an example given in section 4.4.2:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

Note that there is no client_id or client_secret in the POST body. They are instead included in the HTTP Authorization header.