Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [UNRELEASED]

### Added

* Support for `BearerTokenAuth`.

### Removed

* Drop support for Python 3.8

## 0.28.1 (6th December, 2024)

* Fix SSL case where `verify=False` together with client side certificates.

## 0.28.0 (28th November, 2024)

Be aware that the default *JSON request bodies now use a more compact representation*. This is generally considered a prefered style, tho may require updates to test suites.
Expand Down
23 changes: 21 additions & 2 deletions docs/advanced/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Or configured on the client instance, ensuring that all outgoing requests will i

## Basic authentication

HTTP basic authentication is an unencrypted authentication scheme that uses a simple encoding of the username and password in the request `Authorization` header. Since it is unencrypted it should typically only be used over `https`, although this is not strictly enforced.
HTTP basic authentication ([RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617)) is an unencrypted authentication scheme that uses a simple encoding of the username and password in the request `Authorization` header. Since it is unencrypted it should typically only be used over `https`, although this is not strictly enforced.

```pycon
>>> auth = httpx.BasicAuth(username="finley", password="secret")
Expand All @@ -26,9 +26,28 @@ HTTP basic authentication is an unencrypted authentication scheme that uses a si
<Response [200 OK]>
```

## Bearer Token authentication

Bearer Token authentication ([RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)) is an unencrypted authentication scheme that uses an API key (Bearer Token) to access OAuth 2.0-protected resources.
There are three variants to transmit the Token:

* `Authorization` Request Header Field
* Form-Encoded Body Parameter (not implemented)
* URI Query Parameter (not implemented)

Since it is unencrypted it should typically only be used over `https`, although this is not strictly enforced.

```pycon
>>> auth = httpx.BearerTokenAuth(bearer_token="secret")
>>> client = httpx.Client(auth=auth)
>>> response = client.get("https://httpbin.org/bearer")
>>> response
<Response [200 OK]>
```

## Digest authentication

HTTP digest authentication is a challenge-response authentication scheme. Unlike basic authentication it provides encryption, and can be used over unencrypted `http` connections. It requires an additional round-trip in order to negotiate the authentication.
HTTP digest authentication ([RFC 7616](https://datatracker.ietf.org/doc/html/rfc7616)) is a challenge-response authentication scheme. Unlike basic authentication it provides encryption, and can be used over unencrypted `http` connections. It requires an additional round-trip in order to negotiate the authentication.

```pycon
>>> auth = httpx.DigestAuth(username="olivia", password="secret")
Expand Down
1 change: 1 addition & 0 deletions httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def main() -> None: # type: ignore
"Auth",
"BaseTransport",
"BasicAuth",
"BearerTokenAuth",
"ByteStream",
"Client",
"CloseError",
Expand Down
27 changes: 26 additions & 1 deletion httpx/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from hashlib import _Hash


__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]
__all__ = ["Auth", "BasicAuth", "BearerTokenAuth", "DigestAuth", "NetRCAuth"]


class Auth:
Expand Down Expand Up @@ -142,6 +142,31 @@ def _build_auth_header(self, username: str | bytes, password: str | bytes) -> st
return f"Basic {token}"


class BearerTokenAuth(Auth):
"""
Allows the 'auth' argument to be passed as a bearer token string,
and uses HTTP Bearer authentication (RFC 6750).
"""

def __init__(
self,
bearer_token: str | bytes,
variant: typing.Literal["HEADER", "FORM-ENCODED", "QUERY"] = "HEADER",
) -> None:
if variant != "HEADER":
raise NotImplementedError(
f"BearerTokenAuth variant '{variant}' is not yet implemented"
)
self._auth_header = self._build_auth_header(bearer_token)

def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
request.headers["Authorization"] = self._auth_header
yield request

def _build_auth_header(self, bearer_token: str | bytes) -> str:
return f"Bearer {to_bytes(bearer_token).decode()}"


class NetRCAuth(Auth):
"""
Use a 'netrc' file to lookup basic auth credentials based on the url host.
Expand Down
31 changes: 31 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@ def test_basic_auth():
flow.send(response)


def test_bearer_token_auth_header():
auth = httpx.BearerTokenAuth(bearer_token="secret")
request = httpx.Request("GET", "https://www.example.com")

# The initial request should include a bearer token auth header.
flow = auth.sync_auth_flow(request)
request = next(flow)
assert request.headers["Authorization"].startswith("Bearer ")

# No other requests are made.
response = httpx.Response(content=b"Hello, world!", status_code=200)
with pytest.raises(StopIteration):
flow.send(response)


def test_bearer_token_auth_form_encoded():
with pytest.raises(
NotImplementedError,
):
auth = httpx.BearerTokenAuth(bearer_token="secret", variant="FORM-ENCODED")
assert auth # pragma: no cover


def test_bearer_token_auth_query():
with pytest.raises(
NotImplementedError,
):
auth = httpx.BearerTokenAuth(bearer_token="secret", variant="QUERY")
assert auth # pragma: no cover


def test_digest_auth_with_200():
auth = httpx.DigestAuth(username="user", password="pass")
request = httpx.Request("GET", "https://www.example.com")
Expand Down