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
196250Both 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+
253350For more details, see [ ` domain.py ` ] ( ./android_sms_gateway/domain.py ) .
254351
255352## 🌐 HTTP Clients
@@ -274,7 +371,7 @@ client.APIClient(..., http=http.HttpxHttpClient())
274371client.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