0
# Server Verification
1
2
Server-side OAuth request verification for service providers implementing OAuth 1.0 protected resources. The Server class validates incoming requests, checks signatures, and extracts verified parameters for application use.
3
4
## Capabilities
5
6
### OAuth Request Verification
7
8
The Server class provides comprehensive request verification including signature validation, timestamp checking, version verification, and parameter extraction.
9
10
```python { .api }
11
class Server:
12
def __init__(self, signature_methods: dict = None):
13
"""
14
Initialize OAuth server with supported signature methods.
15
16
Args:
17
signature_methods (dict): Dictionary of signature method name to instance mappings.
18
If None, defaults to empty dict.
19
"""
20
21
def add_signature_method(self, signature_method) -> dict:
22
"""
23
Add signature method to server.
24
25
Args:
26
signature_method: SignatureMethod instance
27
28
Returns:
29
dict: Updated signature methods dictionary
30
"""
31
32
def verify_request(self, request, consumer, token) -> dict:
33
"""
34
Verify OAuth request and return non-OAuth parameters.
35
36
Args:
37
request: Request object to verify
38
consumer: Consumer credentials to verify against
39
token: Token credentials to verify against
40
41
Returns:
42
dict: Non-OAuth parameters from verified request
43
44
Raises:
45
Error: If verification fails (invalid signature, expired timestamp, etc.)
46
MissingSignature: If oauth_signature parameter is missing
47
"""
48
49
def build_authenticate_header(self, realm: str = '') -> dict:
50
"""
51
Build WWW-Authenticate header for 401 responses.
52
53
Args:
54
realm (str): OAuth realm parameter
55
56
Returns:
57
dict: Header dictionary with WWW-Authenticate
58
"""
59
```
60
61
### Server Configuration
62
63
```python { .api }
64
# Server properties
65
Server.timestamp_threshold = 300 # Time threshold in seconds (default: 5 minutes)
66
Server.version = '1.0' # OAuth version
67
```
68
69
## Usage Examples
70
71
### Basic Request Verification
72
73
```python
74
import oauth2
75
76
# Set up server with supported signature methods
77
server = oauth2.Server()
78
server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())
79
server.add_signature_method(oauth2.SignatureMethod_PLAINTEXT())
80
81
# Consumer from your database/store
82
consumer = oauth2.Consumer('known_consumer_key', 'known_consumer_secret')
83
84
# Token from your database/store
85
token = oauth2.Token('known_token_key', 'known_token_secret')
86
87
# Create request from incoming HTTP request
88
request = oauth2.Request.from_request(
89
http_method='GET',
90
http_url='https://api.myservice.com/protected_resource',
91
headers={'Authorization': 'OAuth oauth_consumer_key="known_consumer_key"...'},
92
query_string='param1=value1¶m2=value2'
93
)
94
95
try:
96
# Verify the request
97
parameters = server.verify_request(request, consumer, token)
98
print(f"Verified parameters: {parameters}")
99
100
# Process the authenticated request
101
# parameters contains non-OAuth query parameters
102
103
except oauth2.Error as e:
104
print(f"OAuth verification failed: {e}")
105
# Return 401 Unauthorized with authenticate header
106
auth_header = server.build_authenticate_header(realm='api.myservice.com')
107
```
108
109
### Web Framework Integration (Django Example)
110
111
```python
112
import oauth2
113
from django.http import HttpResponse, HttpResponseBadRequest
114
from django.views.decorators.csrf import csrf_exempt
115
116
# Initialize server
117
oauth_server = oauth2.Server()
118
oauth_server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())
119
120
def get_consumer(consumer_key):
121
"""Look up consumer from database."""
122
# Replace with your consumer lookup logic
123
if consumer_key == 'valid_key':
124
return oauth2.Consumer('valid_key', 'valid_secret')
125
return None
126
127
def get_token(token_key):
128
"""Look up token from database."""
129
# Replace with your token lookup logic
130
if token_key == 'valid_token':
131
return oauth2.Token('valid_token', 'valid_token_secret')
132
return None
133
134
@csrf_exempt
135
def protected_resource(request):
136
"""Protected API endpoint."""
137
138
# Create OAuth request from Django request
139
oauth_request = oauth2.Request.from_request(
140
http_method=request.method,
141
http_url=request.build_absolute_uri(),
142
headers=request.META,
143
query_string=request.META.get('QUERY_STRING', '')
144
)
145
146
if not oauth_request:
147
return HttpResponseBadRequest("Invalid OAuth request")
148
149
# Look up consumer
150
consumer_key = oauth_request.get('oauth_consumer_key')
151
consumer = get_consumer(consumer_key)
152
if not consumer:
153
response = HttpResponse("Unauthorized", status=401)
154
auth_header = oauth_server.build_authenticate_header()
155
response['WWW-Authenticate'] = auth_header['WWW-Authenticate']
156
return response
157
158
# Look up token
159
token_key = oauth_request.get('oauth_token')
160
token = get_token(token_key) if token_key else None
161
162
try:
163
# Verify request
164
parameters = oauth_server.verify_request(oauth_request, consumer, token)
165
166
# Process authenticated request
167
return HttpResponse(f"Success! Parameters: {parameters}")
168
169
except oauth2.Error as e:
170
response = HttpResponse(f"OAuth error: {e}", status=401)
171
auth_header = oauth_server.build_authenticate_header()
172
response['WWW-Authenticate'] = auth_header['WWW-Authenticate']
173
return response
174
```
175
176
### Flask Integration Example
177
178
```python
179
import oauth2
180
from flask import Flask, request, jsonify, make_response
181
182
app = Flask(__name__)
183
184
# OAuth server setup
185
oauth_server = oauth2.Server()
186
oauth_server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())
187
188
def oauth_required(f):
189
"""Decorator for OAuth-protected endpoints."""
190
def decorated_function(*args, **kwargs):
191
# Create OAuth request
192
oauth_request = oauth2.Request.from_request(
193
http_method=request.method,
194
http_url=request.url,
195
headers=dict(request.headers),
196
query_string=request.query_string.decode('utf-8')
197
)
198
199
if not oauth_request:
200
return make_response("Invalid OAuth request", 400)
201
202
# Look up consumer and token (implement your lookup logic)
203
consumer = lookup_consumer(oauth_request.get('oauth_consumer_key'))
204
token = lookup_token(oauth_request.get('oauth_token'))
205
206
if not consumer:
207
response = make_response("Unauthorized", 401)
208
auth_header = oauth_server.build_authenticate_header()
209
response.headers['WWW-Authenticate'] = auth_header['WWW-Authenticate']
210
return response
211
212
try:
213
# Verify request
214
parameters = oauth_server.verify_request(oauth_request, consumer, token)
215
216
# Add verified parameters to request context
217
request.oauth_parameters = parameters
218
request.oauth_consumer = consumer
219
request.oauth_token = token
220
221
return f(*args, **kwargs)
222
223
except oauth2.Error as e:
224
response = make_response(f"OAuth error: {e}", 401)
225
auth_header = oauth_server.build_authenticate_header()
226
response.headers['WWW-Authenticate'] = auth_header['WWW-Authenticate']
227
return response
228
229
decorated_function.__name__ = f.__name__
230
return decorated_function
231
232
@app.route('/api/protected')
233
@oauth_required
234
def protected_endpoint():
235
"""OAuth-protected API endpoint."""
236
return jsonify({
237
'message': 'Success!',
238
'parameters': request.oauth_parameters,
239
'consumer': request.oauth_consumer.key
240
})
241
```
242
243
### Custom Signature Method
244
245
```python
246
import oauth2
247
import hashlib
248
249
class SignatureMethod_SHA256(oauth2.SignatureMethod):
250
"""Custom SHA256-based signature method."""
251
name = 'HMAC-SHA256'
252
253
def signing_base(self, request, consumer, token):
254
"""Calculate signing base and key."""
255
sig = (
256
oauth2.escape(request.method),
257
oauth2.escape(request.normalized_url),
258
oauth2.escape(request.get_normalized_parameters()),
259
)
260
261
key = f"{oauth2.escape(consumer.secret)}&"
262
if token:
263
key += oauth2.escape(token.secret)
264
265
raw = '&'.join(sig)
266
return key.encode('ascii'), raw.encode('ascii')
267
268
def sign(self, request, consumer, token):
269
"""Generate SHA256 signature."""
270
import hmac
271
import base64
272
273
key, raw = self.signing_base(request, consumer, token)
274
hashed = hmac.new(key, raw, hashlib.sha256)
275
return base64.b64encode(hashed.digest())[:-1]
276
277
# Use custom signature method
278
server = oauth2.Server()
279
server.add_signature_method(SignatureMethod_SHA256())
280
server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1()) # Fallback
281
```
282
283
### Error Handling and Debugging
284
285
```python
286
import oauth2
287
288
server = oauth2.Server()
289
server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())
290
291
try:
292
parameters = server.verify_request(request, consumer, token)
293
294
except oauth2.MissingSignature:
295
# oauth_signature parameter is missing
296
return error_response("Missing signature", 401)
297
298
except oauth2.Error as e:
299
error_msg = str(e)
300
301
if "Invalid signature" in error_msg:
302
# Signature calculation mismatch
303
# Log expected signature base string for debugging
304
print(f"Signature verification failed: {error_msg}")
305
306
elif "Expired timestamp" in error_msg:
307
# Request timestamp is too old
308
print(f"Timestamp expired: {error_msg}")
309
310
elif "OAuth version" in error_msg:
311
# Unsupported OAuth version
312
print(f"Version error: {error_msg}")
313
314
return error_response(f"OAuth error: {error_msg}", 401)
315
316
def error_response(message, status_code):
317
"""Return error response with proper headers."""
318
response = make_response(message, status_code)
319
if status_code == 401:
320
auth_header = server.build_authenticate_header(realm='api.example.com')
321
response.headers.update(auth_header)
322
return response
323
```
324
325
### Timestamp Configuration
326
327
```python
328
import oauth2
329
330
# Create server with custom timestamp threshold
331
server = oauth2.Server()
332
server.timestamp_threshold = 600 # Allow 10 minutes clock skew
333
334
# Or modify global default
335
oauth2.Server.timestamp_threshold = 120 # 2 minutes
336
337
server.add_signature_method(oauth2.SignatureMethod_HMAC_SHA1())
338
```
339
340
## Security Considerations
341
342
1. **Timestamp Validation**: Default 5-minute threshold prevents replay attacks
343
2. **Nonce Tracking**: Implement nonce storage to prevent duplicate requests
344
3. **HTTPS Only**: Always use HTTPS in production for credential protection
345
4. **Consumer/Token Storage**: Securely store consumer secrets and token secrets
346
5. **Signature Method**: HMAC-SHA1 is recommended over PLAINTEXT
347
6. **Error Messages**: Avoid exposing sensitive information in error responses