Skip to content

Commit b3c37ce

Browse files
committed
Add JWT token authentication
1 parent e1dfd2e commit b3c37ce

File tree

5 files changed

+41
-7
lines changed

5 files changed

+41
-7
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Unreleased
66
==========
77
- Modernize project definition to latest Python best practices. Thanks, @surister.
88
- Exceptions: Exceptions from the BLOB API now include their full names.
9+
- Added JWT token authentication
910

1011
2025/01/30 2.0.0
1112
================

docs/connect.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ and password.
241241
authenticate as the CrateDB superuser, which is ``crate``. The superuser
242242
does not have a password, so you can omit the ``password`` argument.
243243

244+
Alternatively, authenticate using a JWT token:
245+
246+
>>> connection = client.connect(..., jwt_token="<JWT_TOKEN>")
247+
248+
Here, replace ``<JWT_TOKEN>`` with the appropriate JWT token.
249+
244250
.. _schema-selection:
245251

246252
Schema selection

src/crate/client/connection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(
4242
ssl_relax_minimum_version=False,
4343
username=None,
4444
password=None,
45+
jwt_token=None,
4546
schema=None,
4647
pool_size=None,
4748
socket_keepalive=True,
@@ -81,6 +82,8 @@ def __init__(
8182
the username in the database.
8283
:param password:
8384
the password of the user in the database.
85+
:param jwt_token:
86+
the JWT token to authenticate with the server.
8487
:param pool_size:
8588
(optional)
8689
Number of connections to save that can be reused.
@@ -148,6 +151,7 @@ def __init__(
148151
ssl_relax_minimum_version=ssl_relax_minimum_version,
149152
username=username,
150153
password=password,
154+
jwt_token=jwt_token,
151155
schema=schema,
152156
pool_size=pool_size,
153157
socket_keepalive=socket_keepalive,

src/crate/client/http.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def request(
158158
headers=None,
159159
username=None,
160160
password=None,
161+
jwt_token=None,
161162
schema=None,
162163
backoff_factor=0,
163164
**kwargs,
@@ -173,6 +174,10 @@ def request(
173174
if length is not None:
174175
headers["Content-Length"] = length
175176

177+
# Authentication token
178+
if jwt_token is not None and "Authorization" not in headers:
179+
headers["Authorization"] = "Bearer %s" % jwt_token
180+
176181
# Authentication credentials
177182
if username is not None:
178183
if "Authorization" not in headers and username is not None:
@@ -421,6 +426,7 @@ def __init__(
421426
ssl_relax_minimum_version=False,
422427
username=None,
423428
password=None,
429+
jwt_token=None,
424430
schema=None,
425431
pool_size=None,
426432
socket_keepalive=True,
@@ -477,6 +483,7 @@ def __init__(
477483
self._local = threading.local()
478484
self.username = username
479485
self.password = password
486+
self.jwt_token = jwt_token
480487
self.schema = schema
481488

482489
self.path = self.SQL_PATH
@@ -593,6 +600,7 @@ def _request(self, method, path, server=None, **kwargs):
593600
path,
594601
username=self.username,
595602
password=self.password,
603+
jwt_token=self.jwt_token,
596604
backoff_factor=self.backoff_factor,
597605
schema=self.schema,
598606
**kwargs,

tests/client/test_http.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -632,14 +632,22 @@ def do_POST(self):
632632
self.server.SHARED["schema"] = self.headers.get("Default-Schema")
633633

634634
if self.headers.get("Authorization") is not None:
635-
auth_header = self.headers["Authorization"].replace("Basic ", "")
636-
credentials = b64decode(auth_header).decode("utf-8").split(":", 1)
637-
self.server.SHARED["username"] = credentials[0]
638-
if len(credentials) > 1 and credentials[1]:
639-
self.server.SHARED["password"] = credentials[1]
640-
else:
641-
self.server.SHARED["password"] = None
635+
auth_header = self.headers["Authorization"]
636+
if "Basic" in auth_header:
637+
auth_header = auth_header.replace("Basic ", "")
638+
credentials = (
639+
b64decode(auth_header).decode("utf-8").split(":", 1)
640+
)
641+
self.server.SHARED["username"] = credentials[0]
642+
if len(credentials) > 1 and credentials[1]:
643+
self.server.SHARED["password"] = credentials[1]
644+
else:
645+
self.server.SHARED["password"] = None
646+
elif "Bearer" in auth_header:
647+
jwt_token = auth_header.replace("Bearer ", "")
648+
self.server.SHARED["jwt_token"] = jwt_token
642649
else:
650+
self.server.SHARED["jwt_token"] = None
643651
self.server.SHARED["username"] = None
644652

645653
if self.headers.get("X-User") is not None:
@@ -705,3 +713,10 @@ def test_credentials(serve_http):
705713
assert server.SHARED["usernameFromXUser"] == username
706714
assert server.SHARED["username"] == username
707715
assert server.SHARED["password"] == password
716+
717+
# Just a single token, most convenient.
718+
jwt_token = "testJwtToken"
719+
with connect(url, jwt_token=jwt_token) as conn:
720+
assert conn.client.jwt_token == jwt_token
721+
conn.client.sql("select 3;")
722+
assert server.SHARED["jwt_token"] == jwt_token

0 commit comments

Comments
 (0)