couper icon indicating copy to clipboard operation
couper copied to clipboard

Send WWW-Authenticate response header and appropriate status code for unauthorized bearer requests

Open johakoch opened this issue 4 years ago • 4 comments

The OAuth 2.0 Authorization Framework: Bearer Token Usage

(This is the RFC defining Bearer Authorization.)

Section "3. The WWW-Authenticate Response Header Field":

If the protected resource request does not include authentication credentials or does not contain an access token that enables access to the protected resource, the resource server MUST include the HTTP "WWW-Authenticate" response header field; it MAY include it in response to other conditions as well.

If the protected resource request included an access token and failed authentication, the resource server SHOULD include the "error" attribute to provide the client with the reason why the access request was declined. The parameter value is described in Section 3.1. In addition, the resource server MAY include the "error_description" attribute to provide developers a human-readable explanation that is not meant to be displayed to end-users. It also MAY include the "error_uri" attribute with an absolute URI identifying a human-readable web page explaining the error. The "error", "error_description", and "error_uri" attributes MUST NOT appear more than once.

Example:

     HTTP/1.1 401 Unauthorized
     WWW-Authenticate: Bearer realm="example",
                       error="invalid_token",
                       error_description="The access token expired"

Section "3.1. Error Codes":

invalid_token The access token provided is expired, revoked, malformed, or invalid for other reasons. The resource SHOULD respond with the HTTP 401 (Unauthorized) status code. The client MAY request a new access token and retry the protected resource request.

Currently, the status code for a rejected request (lacking authrorization or with an invalid token) is 403 (Forbidden), which SHOULD be used for insufficient privileges:

insufficient_scope The request requires higher privileges than provided by the access token. The resource server SHOULD respond with the HTTP 403 (Forbidden) status code and MAY include the "scope" attribute with the scope necessary to access the protected resource.

johakoch avatar Sep 30 '20 12:09 johakoch

A web browser does not react in a special way to WWW-Authenticate: Bearer ... as is does with WWW-Authenticate: Basic ..., probably because there is no defined user interaction involved.

However, a client application should be able to deal with the various defined error types.

johakoch avatar Oct 08 '20 11:10 johakoch

...the resource server MUST include the HTTP "WWW-Authenticate" response header field;

Couper is not the resource server.

alex-schneider avatar May 06 '21 14:05 alex-schneider

As Couper is a lightweight API gateway, it can be client or resource server or both.

johakoch avatar May 06 '21 14:05 johakoch

Couper sends a 401 if a token is missing.

https://datatracker.ietf.org/doc/html/rfc7235#section-3.1:

The server generating a 401 response MUST send a WWW-Authenticate header field (Section 4.1) containing at least one challenge applicable to the target resource.

johakoch avatar Nov 09 '21 09:11 johakoch

cf. Google APIs and MS Graph. Both respond with 401 and send WWW-Authenticate.

Google APIs lacking token:

$ curl -X POST -si https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values:batchUpdate
HTTP/2 401
www-authenticate: Bearer realm="https://accounts.google.com/"
...

Google APIs invalid token:

$ curl -X POST -H "Authorization: Bearer aobnqbnbqibn" -si https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values:batchUpdate
HTTP/2 401
www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"
...

Google APIs expired token:

$ curl -X POST -H "Authorization: Bearer {expired_token}" -si https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values:batchUpdate
HTTP/2 401
www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"

MS Graph lacking token:

$ curl -si https://graph.microsoft.com/v1.0/me
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="", authorization_uri="https://login.microsoftonline.com/common/oauth2/authorize", client_id="00000003-0000-0000-c000-000000000000"
...

MS Graph invalid token:

$ curl -si -H "Authorization: Bearer eyibnqwpbin.wirnbwpib.wrpibnrb" https://graph.microsoft.com/v1.0/me
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="", authorization_uri="https://login.microsoftonline.com/common/oauth2/authorize", client_id="00000003-0000-0000-c000-000000000000"

johakoch avatar Feb 03 '23 11:02 johakoch

Couper's OAuth2 Backend Request Authorizer expects a 401 as the trigger to get a new token:

func (oa *OAuth2ReqAuth) RetryWithToken(req *http.Request, res *http.Response) (bool, error) {
	if res == nil || res.StatusCode != http.StatusUnauthorized {
		return false, nil
	}
	...

johakoch avatar Feb 10 '23 17:02 johakoch

I'm almost convinced :)

Which attributes would you include in the WWW-A… header? Only error and maybe error_description? We don't have a realm and neither an authorization_uri or client_id, do we?

filex avatar Feb 10 '23 17:02 filex

At least error="invalid_token" for jwt_token_invalid and jwt_token_expired; no error for jwt_token_missing, see https://www.rfc-editor.org/rfc/rfc6750#section-3.1:

If the request lacks any authentication information (e.g., the client was unaware that authentication is necessary or attempted using an unsupported authentication method), the resource server SHOULD NOT include an error code or other error information.

johakoch avatar Feb 10 '23 18:02 johakoch

The JWT AC can read the token from several sources:

  • a cookie (cookie = "...")
  • an HTTP request header field (header = "...", "Authorization" being the default token source)
  • some other way (tokenValue = ...)

Should we set the WWW-Authenticate response header only for the header = "Authorization" case? Or more specifically, only for Authorization using the Bearer auth-scheme? The two remaining ways for bearer auth (access_token form parameter and access_token query parameter) could be configured using the tokenValue attribute.

There is also a (quite old) personal draft for a Cookie auth-scheme. But I guess, cookie = "..." is primarily configured in scenarios with first-party clients.

johakoch avatar Feb 13 '23 08:02 johakoch

At least error="invalid_token" for jwt_token_invalid and jwt_token_expired; no error for jwt_token_missing

And maybe an additional , error_description="The access token expired" for "jwt_token_expired".

No WWW-Authenticate response header for cookie = "...". And I would argue, that putting a bearer token into a form_body instead of an Authorization header is quite uncommon (and putting it into an URL query is explicitly not recommended and a SHOULD NOT in RFC6750 and a MUST NOT in OAuth 2.1 draft), so that we could treat token_value = ... as generally not being a bearer token case; so no WWW-Authenticate: Bearer for token_value = ....

johakoch avatar Feb 15 '23 17:02 johakoch