couper
couper copied to clipboard
Send WWW-Authenticate response header and appropriate status code for unauthorized bearer requests
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 HTTP401
(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 HTTP403
(Forbidden) status code and MAY include the "scope
" attribute with the scope necessary to access the protected resource.
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.
...the resource server MUST include the HTTP "WWW-Authenticate" response header field;
Couper is not the resource server.
As Couper is a lightweight API gateway, it can be client or resource server or both.
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.
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"
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
}
...
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?
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.
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.
At least
error="invalid_token"
forjwt_token_invalid
andjwt_token_expired
; noerror
forjwt_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 = ...
.