Skip to content

Commit ad36f70

Browse files
committed
[integration] add JWT sections
1 parent 5d331e5 commit ad36f70

File tree

7 files changed

+959
-2
lines changed

7 files changed

+959
-2
lines changed

docs/faq/authentication.md

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
# ❌ FAQ - Authentication
2+
3+
## 🔐 What is JWT authentication and how does it work?
4+
5+
JWT (JSON Web Token) authentication is the primary authentication mechanism for the SMSGate API. It provides a secure, scalable way to authenticate API requests without transmitting credentials with each request.
6+
7+
## 🔄 How do I migrate from Basic Auth to JWT?
8+
9+
Migrating from Basic Authentication to JWT provides enhanced security, better performance, and fine-grained access control. Here's how to migrate:
10+
11+
### Step 1: Update Your Code
12+
13+
Replace Basic Auth with JWT Bearer tokens:
14+
15+
=== "Before (Basic Auth)"
16+
```python
17+
response = requests.post(
18+
"https://api.sms-gate.app/3rdparty/v1/messages",
19+
auth=("username", "password"),
20+
json={"phoneNumbers": ["+1234567890"], "textMessage": {"text": "Hello world!"}}
21+
)
22+
```
23+
24+
=== "After (JWT)"
25+
```python
26+
# First, get a token
27+
token_response = requests.post(
28+
"https://api.sms-gate.app/3rdparty/v1/auth/token",
29+
auth=("username", "password"),
30+
json={"ttl": 3600, "scopes": ["messages:send"]}
31+
)
32+
33+
if token_response.status_code == 201:
34+
token_data = token_response.json()
35+
access_token = token_data["access_token"]
36+
37+
# Then use the token
38+
response = requests.post(
39+
"https://api.sms-gate.app/3rdparty/v1/messages",
40+
headers={
41+
"Authorization": f"Bearer {access_token}",
42+
"Content-Type": "application/json"
43+
},
44+
json={"phoneNumbers": ["+1234567890"], "textMessage": {"text": "Hello world!"}}
45+
)
46+
```
47+
48+
### Step 2: Implement Token Management
49+
50+
- **Token Refresh**: Implement automatic token refresh before expiration
51+
- **Error Handling**: Handle 401/403 errors gracefully
52+
- **Secure Storage**: Store tokens securely on the server side
53+
54+
## 🔑 What are JWT scopes and how do I use them?
55+
56+
JWT scopes define the permissions associated with a token, implementing the principle of least privilege. All scopes follow the pattern: `resource:action`
57+
58+
All available scopes are listed in the [Authentication](../integration/authentication.md#jwt-scopes-) section.
59+
60+
### Using Scopes
61+
62+
When requesting a JWT token, specify which scopes you need:
63+
64+
```json
65+
{
66+
"ttl": 3600,
67+
"scopes": [
68+
"messages:send",
69+
"messages:read",
70+
"devices:list"
71+
]
72+
}
73+
```
74+
75+
!!! tip "Scope Best Practices"
76+
- Request only the scopes you need
77+
- Create multiple tokens with different scopes for different components
78+
- Use short TTLs for tokens with sensitive scopes
79+
- Avoid using `all:any` unless absolutely necessary
80+
81+
## ⏰ How long do JWT tokens last and how do I refresh them?
82+
83+
JWT tokens have a configurable time-to-live (TTL). The default TTL is 24 hours (86400 seconds), but you can specify a custom duration when generating a token.
84+
85+
### Token Expiration
86+
87+
```json
88+
{
89+
"id": "w8pxz0a4Fwa4xgzyCvSeC",
90+
"token_type": "Bearer",
91+
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
92+
"expires_at": "2025-11-22T08:45:00Z"
93+
}
94+
```
95+
96+
### Token Refresh Strategy
97+
98+
Since JWT tokens cannot be refreshed (they must be reissued), implement a proactive refresh strategy:
99+
100+
```python
101+
import requests
102+
from datetime import datetime, timedelta
103+
104+
class SMSGatewayClient:
105+
def __init__(self, gateway_url, username, password):
106+
self.gateway_url = gateway_url
107+
self.username = username
108+
self.password = password
109+
self.access_token = None
110+
self.token_expires_at = None
111+
112+
def get_token(self, scopes, ttl=3600):
113+
"""Get a new JWT token"""
114+
response = requests.post(
115+
f"{self.gateway_url}/3rdparty/v1/auth/token",
116+
auth=(self.username, self.password),
117+
headers={"Content-Type": "application/json"},
118+
json={"ttl": ttl, "scopes": scopes}
119+
)
120+
121+
if response.status_code == 201:
122+
token_data = response.json()
123+
self.access_token = token_data["access_token"]
124+
self.token_expires_at = datetime.fromisoformat(
125+
token_data["expires_at"].replace("Z", "+00:00")
126+
)
127+
return self.access_token
128+
else:
129+
raise Exception(f"Failed to get token: {response.status_code}")
130+
131+
def ensure_valid_token(self, scopes):
132+
"""Ensure we have a valid token, refresh if needed"""
133+
if (self.access_token is None or
134+
self.token_expires_at is None or
135+
datetime.now() + timedelta(minutes=5) >= self.token_expires_at):
136+
return self.get_token(scopes)
137+
return self.access_token
138+
```
139+
140+
!!! tip "Token Management Best Practices"
141+
- Refresh tokens 5-10 minutes before expiration
142+
- Implement exponential backoff for failed refresh attempts
143+
- Store tokens securely (not in client-side code)
144+
145+
## 🛡️ How do I revoke a JWT token?
146+
147+
JWT tokens can be revoked before they expire using the token revocation endpoint. This is useful when a token is compromised or no longer needed.
148+
149+
### Revoking a Token
150+
151+
```bash
152+
curl -X DELETE "https://api.sms-gate.app/3rdparty/v1/auth/token/{jti}" \
153+
-H "Authorization: Basic username:password"
154+
```
155+
156+
Where `{jti}` is the token ID from the token response.
157+
158+
## 🔐 "Invalid token" JWT Error
159+
160+
The "invalid token" error occurs when the JWT token is malformed, has an incorrect signature, or cannot be validated by the server.
161+
162+
### Common Causes
163+
164+
1. **Malformed Token**: The token structure is incorrect or corrupted
165+
2. **Invalid Signature**: The token signature doesn't match the server's secret
166+
3. **Algorithm Mismatch**: The token was signed with a different algorithm than expected
167+
4. **Encoding Issues**: The token contains invalid characters or formatting
168+
169+
### Troubleshooting Steps
170+
171+
1. **Check Token Format**: Ensure the token has three parts separated by dots (`.`)
172+
```
173+
header.payload.signature
174+
```
175+
176+
2. **Verify Token Copy**: Make sure you copied the entire token without extra spaces or line breaks
177+
178+
3. **Validate Token Structure**: Use an online JWT decoder to verify the token structure
179+
```bash
180+
# Check token structure
181+
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | tr '.' '\n'
182+
```
183+
184+
## ⏰ "Token expired" JWT Error
185+
186+
The "token expired" error occurs when the JWT token has passed its expiration time. This is a normal part of the JWT lifecycle and requires token refresh.
187+
188+
### Common Causes
189+
190+
1. **Token TTL Expired**: The token has reached its expiration time
191+
2. **Clock Skew**: Time differences between client and server clocks
192+
3. **Long-running Operations**: Operations that take longer than the token TTL
193+
194+
### Troubleshooting Steps
195+
196+
1. **Check Expiration Time**: Parse the token to see when it expires
197+
```python
198+
import jwt
199+
import datetime
200+
201+
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
202+
try:
203+
decoded = jwt.decode(token, options={"verify_signature": False})
204+
exp_time = datetime.datetime.fromtimestamp(decoded['exp'])
205+
print(f"Token expires at: {exp_time}")
206+
except Exception as e:
207+
print(f"Error decoding token: {e}")
208+
```
209+
210+
2. **Implement Token Refresh**: Refresh tokens before they expire
211+
```python
212+
# Refresh token 5 minutes before expiration
213+
if datetime.now() + timedelta(minutes=5) >= token_expires_at:
214+
new_token = get_new_token()
215+
```
216+
217+
3. **Adjust Token TTL**: Use a longer TTL for long-running operations
218+
```json
219+
{
220+
"ttl": 7200, // 2 hours instead of 1 hour
221+
"scopes": ["messages:send", "messages:read"]
222+
}
223+
```
224+
225+
!!! tip "Best Practices"
226+
- Implement automatic token refresh
227+
- Use appropriate TTL values for your use case
228+
- Handle token expiration gracefully in your code
229+
- Consider clock skew in your expiration logic
230+
231+
## 🚫 "Token revoked" JWT Error
232+
233+
The "token revoked" error occurs when a JWT token has been manually revoked before its natural expiration time.
234+
235+
### Common Causes
236+
237+
1. **Manual Revocation**: Token was explicitly revoked by an administrator
238+
2. **Security Incident**: Token was revoked due to a security concern
239+
240+
### Troubleshooting Steps
241+
242+
1. **Request New Token**: Generate a new token with the same scopes
243+
```bash
244+
curl -X POST "https://api.sms-gate.app/3rdparty/v1/auth/token" \
245+
-u "username:password" \
246+
-H "Content-Type: application/json" \
247+
-d '{
248+
"ttl": 3600,
249+
"scopes": ["messages:send", "messages:read"]
250+
}'
251+
```
252+
253+
2. **Investigate Revocation Reason**: Contact support to understand why the token was revoked
254+
255+
## 🙅 "Scope required" JWT Error
256+
257+
The "scope required" error occurs when the JWT token doesn't have the necessary scope to access a specific resource or perform a specific action.
258+
259+
### Common Causes
260+
261+
1. **Missing Scope**: The token doesn't include the required scope
262+
2. **Incorrect Scope**: The token has the wrong scope for the requested action
263+
3. **Scope Typos**: The scope name is misspelled or incorrectly formatted
264+
4. **Resource Changes**: The required scope for a resource has changed
265+
266+
### Troubleshooting Steps
267+
268+
1. **Check Token Scopes**: Verify what scopes your token contains
269+
```python
270+
import jwt
271+
272+
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
273+
try:
274+
decoded = jwt.decode(token, options={"verify_signature": False})
275+
print("Token scopes:", decoded.get('scopes', []))
276+
except Exception as e:
277+
print(f"Error decoding token: {e}")
278+
```
279+
280+
2. **Verify Required Scope**: Check the API documentation for the required scope
281+
```
282+
GET /3rdparty/v1/messages requires: messages:list
283+
POST /3rdparty/v1/messages requires: messages:send
284+
```
285+
286+
3. **Request New Token**: Generate a new token with the correct scopes
287+
```json
288+
{
289+
"ttl": 3600,
290+
"scopes": [
291+
"messages:send",
292+
"messages:read",
293+
"devices:list"
294+
]
295+
}
296+
```
297+
298+
!!! tip "Scope Best Practices"
299+
- Request only the scopes you need
300+
- Use the exact scope names from the documentation
301+
- Create multiple tokens for different purposes
302+
- Regularly review and update your scope requirements
303+
304+
## 🔄 "Migration from Basic Auth to JWT" Issues
305+
306+
When migrating from Basic Authentication to JWT, you may encounter various issues. Here are common problems and their solutions.
307+
308+
### Common Issues
309+
310+
1. **Token Generation Errors**: Unable to generate JWT tokens
311+
2. **Permission Errors**: JWT tokens don't have the same permissions as Basic Auth
312+
3. **Code Compatibility**: Existing code doesn't work with JWT authentication
313+
314+
### Troubleshooting Steps
315+
316+
1. **Verify Token Generation**: Ensure you can generate JWT tokens successfully
317+
```bash
318+
curl -X POST "https://api.sms-gate.app/3rdparty/v1/auth/token" \
319+
-u "username:password" \
320+
-H "Content-Type: application/json" \
321+
-d '{"ttl": 3600, "scopes": ["messages:send"]}'
322+
```
323+
324+
2. **Update Code Gradually**: Migrate code incrementally rather than all at once
325+
```python
326+
# Hybrid approach during migration
327+
def make_request(endpoint, data=None, use_jwt=True):
328+
if use_jwt and jwt_token:
329+
headers = {"Authorization": f"Bearer {jwt_token}"}
330+
else:
331+
# Fall back to Basic Auth
332+
headers = {}
333+
auth = (username, password)
334+
335+
return requests.post(endpoint, headers=headers, auth=auth, json=data)
336+
```
337+
338+
3. **Test in Staging**: Test JWT authentication in a staging environment before production
339+
340+
!!! tip "Migration Best Practices"
341+
- Keep Basic Auth as a fallback during transition
342+
- Monitor authentication errors during migration
343+
344+
## 🛡️ JWT Security Issues
345+
346+
JWT tokens are generally secure, but improper implementation can lead to security vulnerabilities.
347+
348+
### Common Security Issues
349+
350+
1. **Long TTLs**: Using excessively long token expiration times
351+
2. **Token Leakage**: Tokens being exposed in logs, browser storage, or network traffic
352+
3. **Insufficient Scopes**: Using overly broad scopes like `all:any`
353+
354+
### Troubleshooting Steps
355+
356+
1. **Review Token TTL**: Ensure your token TTL is appropriate for your use case
357+
```json
358+
{
359+
"ttl": 3600, // 1 hour - reasonable for most use cases
360+
"scopes": ["messages:send"]
361+
}
362+
```
363+
364+
2. **Implement Secure Storage**: Ensure tokens are stored securely
365+
```python
366+
# Example of secure token storage
367+
from cryptography.fernet import Fernet
368+
369+
key = Fernet.generate_key()
370+
cipher_suite = Fernet(key)
371+
encrypted_token = cipher_suite.encrypt(jwt_token.encode())
372+
```
373+
374+
!!! tip "Security Best Practices"
375+
- Use the shortest practical TTL for your use case
376+
- Store tokens securely on the server side
377+
- Implement proper token revocation

docs/faq/local-server.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ Attempting to connect to the device's API directly can give you an immediate sen
3232

3333
## 🔑 How do I change my password in Local mode? :material-key:
3434

35-
For Local mode, password management is handled through the [Server Configuration](../getting-started/local-server.md#server-configuration) section.
35+
For Local mode, password management is handled through the [Server Configuration](../getting-started/local-server.md#server-configuration) section.
36+
37+
## 🔐 What authentication methods are supported in Local Server mode?
38+
39+
Local Server mode **only supports Basic Authentication**. JWT authentication is not available in this mode. For JWT authentication, please use Public Cloud Server or Private Server modes.

docs/getting-started/local-server.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ This mode is ideal for sending messages from a local network, enabling direct co
1313
3. Tap the status button (labeled `Offline`) at the bottom of the screen to start the server; it will switch to `Online` when running.
1414
4. The `Local Server` section will display your device's local and public IP addresses, as well as the credentials for basic authentication.
1515

16+
!!! warning "Authentication Method"
17+
Local Server mode **only supports Basic Authentication**. JWT authentication is not available in this mode.
18+
1619
!!! note "Public IP Accessibility"
1720
The displayed public IP address is only accessible from the internet if your device has a public IP assigned by your ISP and your firewall/router allows connections to the specified port (with port forwarding configured). Many ISPs use Carrier-Grade NAT (CG‑NAT), which prevents direct internet access to devices behind shared addresses. See also: [FAQ — Local Server](../faq/local-server.md).
1821

0 commit comments

Comments
 (0)