Skip to content

Commit 94834f2

Browse files
committed
[docs] add JWT sections
1 parent 5598245 commit 94834f2

File tree

1 file changed

+123
-16
lines changed

1 file changed

+123
-16
lines changed

README.md

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[![GitHub Forks](https://img.shields.io/github/forks/android-sms-gateway/client-py.svg?style=for-the-badge)](https://github.com/android-sms-gateway/client-py/network)
1010
[![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/android-sms-gateway/client-py?style=for-the-badge)](https://www.coderabbit.ai)
1111

12-
A modern Python client for seamless integration with the [SMS Gateway for Android](https://sms-gate.app) API. Send SMS messages programmatically through your Android devices with this powerful yet simple-to-use library.
12+
A modern Python client for seamless integration with the [SMSGate](https://sms-gate.app) API. Send SMS messages programmatically through your Android devices with this powerful yet simple-to-use library.
1313

1414
## 📖 About The Project
1515

@@ -38,18 +38,22 @@ This client abstracts away the complexities of the underlying HTTP API while pro
3838
- [🚀 Quickstart](#-quickstart)
3939
- [Initial Setup](#initial-setup)
4040
- [Encryption Example](#encryption-example)
41+
- [JWT Authentication Example](#jwt-authentication-example)
4142
- [🤖 Client Guide](#-client-guide)
4243
- [Client Configuration](#client-configuration)
4344
- [Available Methods](#available-methods)
4445
- [Data Structures](#data-structures)
4546
- [Message](#message)
4647
- [MessageState](#messagestate)
4748
- [Webhook](#webhook)
49+
- [TokenRequest](#tokenrequest)
50+
- [TokenResponse](#tokenresponse)
4851
- [🌐 HTTP Clients](#-http-clients)
4952
- [Using Specific Clients](#using-specific-clients)
5053
- [Custom HTTP Client](#custom-http-client)
5154
- [🔒 Security](#-security)
5255
- [Best Practices](#best-practices)
56+
- [JWT Security Best Practices](#jwt-security-best-practices)
5357
- [Secure Configuration Example](#secure-configuration-example)
5458
- [📚 API Reference](#-api-reference)
5559
- [👥 Contributing](#-contributing)
@@ -63,13 +67,15 @@ This client abstracts away the complexities of the underlying HTTP API while pro
6367
## ✨ Features
6468

6569
- 🔄 **Dual Client**: Supports both synchronous (`APIClient`) and asynchronous (`AsyncAPIClient`) interfaces
70+
- 🔐 **Flexible Authentication**: Supports both Basic Auth and JWT token authentication
6671
- 🔒 **End-to-End Encryption**: Optional message encryption using AES-256-CBC
6772
- 🌐 **Multiple HTTP Backends**: Native support for `requests`, `aiohttp`, and `httpx`
6873
- 🔗 **Webhook Management**: Programmatically create, query, and delete webhooks
6974
- ⚙️ **Customizable Base URL**: Point to different API endpoints
7075
- 💻 **Full Type Hinting**: Fully typed for better development experience
7176
- ⚠️ **Robust Error Handling**: Specific exceptions and clear error messages
7277
- 📈 **Delivery Reports**: Track your message delivery status
78+
- 🔑 **Token Management**: Generate and revoke JWT tokens with custom scopes and TTL
7379

7480
## ⚙️ Requirements
7581

@@ -189,29 +195,102 @@ with client.APIClient(login, password, encryptor=encryptor) as c:
189195
print(f"Encrypted message sent: {state.id}")
190196
```
191197

198+
### JWT Authentication Example
199+
200+
```python
201+
import os
202+
from android_sms_gateway import client, domain
203+
204+
# Option 1: Using an existing JWT token
205+
jwt_token = os.getenv("ANDROID_SMS_GATEWAY_JWT_TOKEN")
206+
207+
# Create client with JWT token
208+
with client.APIClient(login=None, password=jwt_token) as c:
209+
message = domain.Message(
210+
phone_numbers=["+1234567890"],
211+
text_message=domain.TextMessage(
212+
text="Hello from JWT authenticated client!",
213+
),
214+
)
215+
216+
# Option 2: Generate a new JWT token with Basic Auth
217+
login = os.getenv("ANDROID_SMS_GATEWAY_LOGIN")
218+
password = os.getenv("ANDROID_SMS_GATEWAY_PASSWORD")
219+
220+
with client.APIClient(login, password) as c:
221+
# Generate a new JWT token with specific scopes and TTL
222+
token_request = domain.TokenRequest(
223+
scopes=["sms:send", "sms:read"],
224+
ttl=3600 # Token expires in 1 hour
225+
)
226+
token_response = c.generate_token(token_request)
227+
print(f"New JWT token: {token_response.access_token}")
228+
print(f"Token expires at: {token_response.expires_at}")
229+
230+
# Use the new token for subsequent requests
231+
with client.APIClient(login=None, password=token_response.access_token) as jwt_client:
232+
message = domain.Message(
233+
phone_numbers=["+1234567890"],
234+
text_message=domain.TextMessage(
235+
text="Hello from newly generated JWT token!",
236+
),
237+
)
238+
state = jwt_client.send(message)
239+
print(f"Message sent with new JWT token: {state.id}")
240+
241+
# Revoke the token when no longer needed
242+
jwt_client.revoke_token(token_response.id)
243+
print(f"Token {token_response.id} has been revoked")
244+
```
245+
192246
## 🤖 Client Guide
193247

194248
### Client Configuration
195249

196250
Both clients (`APIClient` and `AsyncAPIClient`) support these parameters:
197251

198-
| Parameter | Type | Description | Default |
199-
| ----------- | ------------------------------ | ------------------- | ---------------------------------------- |
200-
| `login` | `str` | API username | **Required** |
201-
| `password` | `str` | API password | **Required** |
202-
| `base_url` | `str` | API base URL | `"https://api.sms-gate.app/3rdparty/v1"` |
203-
| `encryptor` | `Encryptor` | Encryption instance | `None` |
204-
| `http` | `HttpClient`/`AsyncHttpClient` | Custom HTTP client | Auto-detected |
252+
| Parameter | Type | Description | Default |
253+
| ----------- | ------------------------------ | ------------------------- | ---------------------------------------- |
254+
| `login` | `str` | API username | **Required** (for Basic Auth) |
255+
| `password` | `str` | API password or JWT token | **Required** |
256+
| `base_url` | `str` | API base URL | `"https://api.sms-gate.app/3rdparty/v1"` |
257+
| `encryptor` | `Encryptor` | Encryption instance | `None` |
258+
| `http` | `HttpClient`/`AsyncHttpClient` | Custom HTTP client | Auto-detected |
259+
260+
**Authentication Options:**
261+
262+
1. **Basic Authentication** (traditional):
263+
```python
264+
client.APIClient(login="username", password="password")
265+
```
266+
267+
2. **JWT Token Authentication**:
268+
```python
269+
# Using an existing JWT token
270+
client.APIClient(login=None, password="your_jwt_token")
271+
272+
# Or generate a token using Basic Auth first
273+
with client.APIClient(login="username", password="password") as c:
274+
token_request = domain.TokenRequest(scopes=["sms:send"], ttl=3600)
275+
token_response = c.generate_token(token_request)
276+
277+
# Use the new token
278+
with client.APIClient(login=None, password=token_response.access_token) as jwt_client:
279+
# Make API calls with JWT authentication
280+
pass
281+
```
205282

206283
### Available Methods
207284

208-
| Method | Description | Return Type |
209-
| ----------------------------------------- | -------------------- | ---------------------- |
210-
| `send(message: domain.Message)` | Send SMS message | `domain.MessageState` |
211-
| `get_state(id: str)` | Check message status | `domain.MessageState` |
212-
| `create_webhook(webhook: domain.Webhook)` | Create new webhook | `domain.Webhook` |
213-
| `get_webhooks()` | List all webhooks | `List[domain.Webhook]` |
214-
| `delete_webhook(id: str)` | Delete webhook | `None` |
285+
| Method | Description | Return Type |
286+
| ---------------------------------------------------- | -------------------- | ---------------------- |
287+
| `send(message: domain.Message)` | Send SMS message | `domain.MessageState` |
288+
| `get_state(id: str)` | Check message status | `domain.MessageState` |
289+
| `create_webhook(webhook: domain.Webhook)` | Create new webhook | `domain.Webhook` |
290+
| `get_webhooks()` | List all webhooks | `List[domain.Webhook]` |
291+
| `delete_webhook(id: str)` | Delete webhook | `None` |
292+
| `generate_token(token_request: domain.TokenRequest)` | Generate JWT token | `domain.TokenResponse` |
293+
| `revoke_token(jti: str)` | Revoke JWT token | `None` |
215294

216295
### Data Structures
217296

@@ -250,6 +329,24 @@ class Webhook:
250329
event: WebhookEvent # Event type
251330
```
252331

332+
#### TokenRequest
333+
334+
```python
335+
class TokenRequest:
336+
scopes: List[str] # List of scopes for the token
337+
ttl: Optional[int] = None # Time to live for the token in seconds
338+
```
339+
340+
#### TokenResponse
341+
342+
```python
343+
class TokenResponse:
344+
access_token: str # The JWT access token
345+
token_type: str # The type of the token (e.g., 'Bearer')
346+
id: str # The unique identifier of the token (jti)
347+
expires_at: str # The expiration time of the token in ISO format
348+
```
349+
253350
For more details, see [`domain.py`](./android_sms_gateway/domain.py).
254351

255352
## 🌐 HTTP Clients
@@ -274,7 +371,7 @@ client.APIClient(..., http=http.HttpxHttpClient())
274371
client.APIClient(..., http=http.RequestsHttpClient())
275372

276373
# Force aiohttp (async only)
277-
async with client.AsyncAPIClient(..., http=http.AiohttpHttpClient()) as c:
374+
async with client.AsyncAPIClient(..., http_client=http.AiohttpHttpClient()) as c:
278375
# ...
279376
```
280377

@@ -294,6 +391,16 @@ Implement your own HTTP client following the `http.HttpClient` (sync) or `ahttp.
294391
- 🔑 **Encryption**: Use end-to-end encryption for sensitive messages
295392
- 🔄 **Rotation**: Regularly rotate your credentials
296393

394+
### JWT Security Best Practices
395+
396+
When using JWT authentication, follow these additional security practices:
397+
398+
- ⏱️ **Short TTL**: Use short time-to-live (TTL) for tokens (recommended: 1 hour or less)
399+
- 🔒 **Secure Storage**: Store JWT tokens securely, preferably in memory or secure storage
400+
- 🎯 **Minimal Scopes**: Request only the minimum necessary scopes for each token
401+
- 🔄 **Token Rotation**: Implement token refresh mechanisms before expiration
402+
- 🛑 **Revocation**: Immediately revoke compromised tokens using `revoke_token()`
403+
297404
### Secure Configuration Example
298405

299406
```python

0 commit comments

Comments
 (0)