Skip to content

Commit ce99c7a

Browse files
committed
Add JWT token authentication
1 parent 5a9f518 commit ce99c7a

File tree

5 files changed

+42
-7
lines changed

5 files changed

+42
-7
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changes for crate
44

55
Unreleased
66
==========
7+
- Added JWT token authentication
78

89
2025/01/30 2.0.0
910
================

docs/connect.rst

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

249+
Alternatively, authenticate using a JWT token:
250+
251+
>>> connection = client.connect(..., jwt_token="<JWT_TOKEN>")
252+
253+
Here, replace ``<JWT_TOKEN>`` with the appropriate JWT token.
254+
249255
.. _schema-selection:
250256

251257
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:
@@ -418,6 +423,7 @@ def __init__(
418423
ssl_relax_minimum_version=False,
419424
username=None,
420425
password=None,
426+
jwt_token=None,
421427
schema=None,
422428
pool_size=None,
423429
socket_keepalive=True,
@@ -473,6 +479,7 @@ def __init__(
473479
self._local = threading.local()
474480
self.username = username
475481
self.password = password
482+
self.jwt_token = jwt_token
476483
self.schema = schema
477484

478485
self.path = self.SQL_PATH
@@ -589,6 +596,7 @@ def _request(self, method, path, server=None, **kwargs):
589596
path,
590597
username=self.username,
591598
password=self.password,
599+
jwt_token=self.jwt_token,
592600
backoff_factor=self.backoff_factor,
593601
schema=self.schema,
594602
**kwargs,

tests/client/test_http.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -571,14 +571,20 @@ def do_POST(self):
571571
self.server.SHARED["schema"] = self.headers.get("Default-Schema")
572572

573573
if self.headers.get("Authorization") is not None:
574-
auth_header = self.headers["Authorization"].replace("Basic ", "")
575-
credentials = b64decode(auth_header).decode("utf-8").split(":", 1)
576-
self.server.SHARED["username"] = credentials[0]
577-
if len(credentials) > 1 and credentials[1]:
578-
self.server.SHARED["password"] = credentials[1]
579-
else:
580-
self.server.SHARED["password"] = None
574+
auth_header = self.headers["Authorization"]
575+
if "Basic" in auth_header:
576+
auth_header = auth_header.replace("Basic ", "")
577+
credentials = b64decode(auth_header).decode("utf-8").split(":", 1)
578+
self.server.SHARED["username"] = credentials[0]
579+
if len(credentials) > 1 and credentials[1]:
580+
self.server.SHARED["password"] = credentials[1]
581+
else:
582+
self.server.SHARED["password"] = None
583+
elif "Bearer" in auth_header:
584+
jwt_token = auth_header.replace("Bearer ", "")
585+
self.server.SHARED["jwt_token"] = jwt_token
581586
else:
587+
self.server.SHARED["jwt_token"] = None
582588
self.server.SHARED["username"] = None
583589

584590
if self.headers.get("X-User") is not None:
@@ -604,6 +610,7 @@ class TestingHTTPServer(HTTPServer):
604610
SHARED = manager.dict()
605611
SHARED["count"] = 0
606612
SHARED["usernameFromXUser"] = None
613+
SHARED["jwt_token"] = None
607614
SHARED["username"] = None
608615
SHARED["password"] = None
609616
SHARED["schema"] = None
@@ -689,13 +696,15 @@ class TestUsernameSentAsHeader(TestingHttpServerTestCase):
689696
def setUp(self):
690697
super().setUp()
691698
self.clientWithoutUsername = self.clientWithKwargs()
699+
self.clientWithJwtToken = self.clientWithKwargs(jwt_token="testJwtToken")
692700
self.clientWithUsername = self.clientWithKwargs(username="testDBUser")
693701
self.clientWithUsernameAndPassword = self.clientWithKwargs(
694702
username="testDBUser", password="test:password"
695703
)
696704

697705
def tearDown(self):
698706
self.clientWithoutUsername.close()
707+
self.clientWithJwtToken.close()
699708
self.clientWithUsername.close()
700709
self.clientWithUsernameAndPassword.close()
701710
super().tearDown()
@@ -720,6 +729,13 @@ def test_username(self):
720729
self.assertEqual(TestingHTTPServer.SHARED["username"], "testDBUser")
721730
self.assertEqual(TestingHTTPServer.SHARED["password"], "test:password")
722731

732+
def test_jwt_token(self):
733+
self.clientWithoutUsername.sql("select * from fake")
734+
self.assertEqual(TestingHTTPServer.SHARED["jwt_token"], None)
735+
736+
self.clientWithJwtToken.sql("select * from fake")
737+
self.assertEqual(TestingHTTPServer.SHARED["jwt_token"], "testJwtToken")
738+
723739

724740
class TestCrateJsonEncoder(TestCase):
725741
def test_naive_datetime(self):

0 commit comments

Comments
 (0)