0
# Multi-Factor Authentication (MFA)
1
2
Complete multi-factor authentication support for AWS Cognito User Pools, including software token (TOTP) and SMS MFA setup, verification, preference management, and challenge responses. Provides enhanced security through two-factor authentication methods.
3
4
## Capabilities
5
6
### Software Token MFA (TOTP)
7
8
Set up and manage Time-based One-Time Password (TOTP) authentication using authenticator apps like Google Authenticator, Authy, or Microsoft Authenticator.
9
10
```python { .api }
11
def associate_software_token(self) -> str:
12
"""
13
Get the secret code for Software Token MFA setup.
14
15
Returns:
16
str: Base32-encoded secret code for TOTP setup
17
18
Usage:
19
- Display as QR code for authenticator app scanning
20
- Provide as manual entry code for authenticator apps
21
- Secret is unique per user and doesn't change
22
23
Requirements:
24
- User must be authenticated (valid access token)
25
- User pool must have MFA enabled
26
"""
27
28
def verify_software_token(self, code: str, device_name: str = "") -> bool:
29
"""
30
Verify TOTP code to complete Software Token MFA registration.
31
32
Args:
33
code (str): 6-digit TOTP code from authenticator app
34
device_name (str, optional): Friendly name for the device
35
36
Returns:
37
bool: True if verification successful, False otherwise
38
39
Actions:
40
- Validates TOTP code against user's secret
41
- Registers the software token for the user
42
- Enables software token MFA for the account
43
44
Note:
45
Must be called after associate_software_token() to complete setup
46
"""
47
```
48
49
**Usage Example:**
50
51
```python
52
from pycognito import Cognito
53
import qrcode
54
import io
55
56
# User must be authenticated
57
u = Cognito('pool-id', 'client-id', username='user')
58
u.authenticate(password='password')
59
60
# Get TOTP secret
61
secret = u.associate_software_token()
62
print(f"Secret code: {secret}")
63
64
# Generate QR code for easy setup
65
totp_uri = f"otpauth://totp/{u.username}?secret={secret}&issuer=MyApp"
66
qr = qrcode.QRCode()
67
qr.add_data(totp_uri)
68
qr.make()
69
70
print("Scan QR code with authenticator app:")
71
qr.print_ascii()
72
73
# User sets up authenticator and provides first code
74
code = input("Enter 6-digit code from authenticator: ")
75
76
# Verify and complete setup
77
if u.verify_software_token(code, "My Phone"):
78
print("Software Token MFA enabled successfully!")
79
else:
80
print("Verification failed. Please try again.")
81
```
82
83
### MFA Preference Management
84
85
Configure MFA preferences for users, including which methods are enabled and which is preferred.
86
87
```python { .api }
88
def set_user_mfa_preference(self, sms_mfa: bool, software_token_mfa: bool, preferred: str = None) -> None:
89
"""
90
Set MFA preferences for the authenticated user.
91
92
Args:
93
sms_mfa (bool): Enable/disable SMS MFA
94
software_token_mfa (bool): Enable/disable Software Token MFA
95
preferred (str, optional): Preferred method ("SMS", "SOFTWARE_TOKEN", or None)
96
97
Valid combinations:
98
- Both False: Disable MFA entirely
99
- One True: Enable that method as preferred
100
- Both True: Enable both, use preferred parameter to choose default
101
102
Requirements:
103
- User must be authenticated
104
- If enabling SMS MFA, phone number must be verified
105
- If enabling Software Token MFA, must call verify_software_token() first
106
107
Raises:
108
ValueError: If preferred value is invalid
109
"""
110
```
111
112
**Usage Example:**
113
114
```python
115
# Enable only Software Token MFA
116
u.set_user_mfa_preference(
117
sms_mfa=False,
118
software_token_mfa=True,
119
preferred="SOFTWARE_TOKEN"
120
)
121
122
# Enable both methods with SMS as preferred
123
u.set_user_mfa_preference(
124
sms_mfa=True,
125
software_token_mfa=True,
126
preferred="SMS"
127
)
128
129
# Disable MFA entirely
130
u.set_user_mfa_preference(
131
sms_mfa=False,
132
software_token_mfa=False
133
)
134
135
print("MFA preferences updated!")
136
```
137
138
### MFA Challenge Responses
139
140
Handle MFA challenges during authentication by responding with appropriate codes.
141
142
```python { .api }
143
def respond_to_software_token_mfa_challenge(self, code: str, mfa_tokens: dict = None) -> None:
144
"""
145
Respond to Software Token MFA challenge during authentication.
146
147
Args:
148
code (str): 6-digit TOTP code from authenticator app
149
mfa_tokens (dict, optional): MFA tokens from exception (uses instance tokens if not provided)
150
151
Actions:
152
- Validates TOTP code
153
- Completes authentication flow
154
- Sets access, ID, and refresh tokens on success
155
156
Usage:
157
Called after catching SoftwareTokenMFAChallengeException during authenticate()
158
"""
159
160
def respond_to_sms_mfa_challenge(self, code: str, mfa_tokens: dict = None) -> None:
161
"""
162
Respond to SMS MFA challenge during authentication.
163
164
Args:
165
code (str): SMS verification code
166
mfa_tokens (dict, optional): MFA tokens from exception (uses instance tokens if not provided)
167
168
Actions:
169
- Validates SMS code
170
- Completes authentication flow
171
- Sets access, ID, and refresh tokens on success
172
173
Usage:
174
Called after catching SMSMFAChallengeException during authenticate()
175
"""
176
```
177
178
**Usage Example:**
179
180
```python
181
from pycognito import Cognito
182
from pycognito.exceptions import (
183
SoftwareTokenMFAChallengeException,
184
SMSMFAChallengeException
185
)
186
187
u = Cognito('pool-id', 'client-id', username='user')
188
189
try:
190
u.authenticate(password='password')
191
print("Authentication successful!")
192
193
except SoftwareTokenMFAChallengeException as e:
194
print("Software Token MFA required")
195
code = input("Enter 6-digit code from authenticator: ")
196
u.respond_to_software_token_mfa_challenge(code, e.get_tokens())
197
print("MFA authentication successful!")
198
199
except SMSMFAChallengeException as e:
200
print("SMS MFA required")
201
code = input("Enter SMS verification code: ")
202
u.respond_to_sms_mfa_challenge(code, e.get_tokens())
203
print("MFA authentication successful!")
204
```
205
206
## Exception Handling
207
208
MFA operations use specialized exceptions to handle different challenge types.
209
210
```python { .api }
211
class MFAChallengeException(WarrantException):
212
"""Base class for MFA challenge exceptions."""
213
214
def __init__(self, message: str, tokens: dict, *args, **kwargs):
215
"""
216
Args:
217
message (str): Exception message
218
tokens (dict): MFA challenge tokens needed for response
219
"""
220
221
def get_tokens(self) -> dict:
222
"""
223
Get MFA challenge tokens.
224
225
Returns:
226
dict: Tokens needed for MFA challenge response
227
"""
228
229
class SoftwareTokenMFAChallengeException(MFAChallengeException):
230
"""Raised when Software Token (TOTP) MFA is required."""
231
232
class SMSMFAChallengeException(MFAChallengeException):
233
"""Raised when SMS MFA is required."""
234
```
235
236
## Usage Patterns
237
238
### Complete TOTP Setup Flow
239
240
```python
241
def setup_totp_mfa(username, password):
242
"""Complete TOTP MFA setup flow."""
243
u = Cognito('pool-id', 'client-id', username=username)
244
245
# Authenticate user
246
u.authenticate(password=password)
247
248
# Get TOTP secret
249
secret = u.associate_software_token()
250
251
print("Set up your authenticator app:")
252
print(f"Manual entry code: {secret}")
253
print(f"Or scan QR code for: otpauth://totp/{username}?secret={secret}&issuer=MyApp")
254
255
# Wait for user to set up authenticator
256
input("Press Enter after setting up authenticator app...")
257
258
# Verify setup
259
while True:
260
code = input("Enter 6-digit code from authenticator: ")
261
262
if u.verify_software_token(code, "Primary Device"):
263
print("TOTP setup successful!")
264
break
265
else:
266
print("Invalid code. Please try again.")
267
268
# Set as preferred MFA method
269
u.set_user_mfa_preference(
270
sms_mfa=False,
271
software_token_mfa=True,
272
preferred="SOFTWARE_TOKEN"
273
)
274
275
print("TOTP MFA is now enabled and preferred!")
276
277
# Usage
278
setup_totp_mfa('user@example.com', 'password')
279
```
280
281
### MFA-Aware Authentication
282
283
```python
284
def authenticate_with_mfa(username, password):
285
"""Authenticate user with MFA support."""
286
u = Cognito('pool-id', 'client-id', username=username)
287
288
try:
289
# Attempt primary authentication
290
u.authenticate(password=password)
291
print("Authentication successful (no MFA required)")
292
return u
293
294
except SoftwareTokenMFAChallengeException as e:
295
print("TOTP MFA required")
296
297
# Get TOTP code from user
298
code = input("Enter 6-digit code from authenticator: ")
299
300
# Respond to challenge
301
u.respond_to_software_token_mfa_challenge(code, e.get_tokens())
302
print("TOTP MFA authentication successful!")
303
return u
304
305
except SMSMFAChallengeException as e:
306
print("SMS MFA required")
307
print("SMS code sent to your registered phone number")
308
309
# Get SMS code from user
310
code = input("Enter SMS verification code: ")
311
312
# Respond to challenge
313
u.respond_to_sms_mfa_challenge(code, e.get_tokens())
314
print("SMS MFA authentication successful!")
315
return u
316
317
except Exception as e:
318
print(f"Authentication failed: {e}")
319
return None
320
321
# Usage
322
user = authenticate_with_mfa('user@example.com', 'password')
323
if user:
324
print(f"Access token: {user.access_token}")
325
```
326
327
### MFA Configuration Management
328
329
```python
330
def manage_mfa_settings(u):
331
"""Interactive MFA settings management."""
332
333
print("\nCurrent MFA Settings:")
334
print("1. Enable TOTP MFA only")
335
print("2. Enable SMS MFA only")
336
print("3. Enable both (TOTP preferred)")
337
print("4. Enable both (SMS preferred)")
338
print("5. Disable MFA")
339
340
choice = input("Select option (1-5): ")
341
342
if choice == "1":
343
# Setup TOTP if not already done
344
secret = u.associate_software_token()
345
print(f"Setup authenticator with: {secret}")
346
code = input("Enter verification code: ")
347
348
if u.verify_software_token(code):
349
u.set_user_mfa_preference(
350
sms_mfa=False,
351
software_token_mfa=True,
352
preferred="SOFTWARE_TOKEN"
353
)
354
print("TOTP MFA enabled!")
355
356
elif choice == "2":
357
u.set_user_mfa_preference(
358
sms_mfa=True,
359
software_token_mfa=False,
360
preferred="SMS"
361
)
362
print("SMS MFA enabled!")
363
364
elif choice == "3":
365
# Ensure TOTP is set up
366
secret = u.associate_software_token()
367
print(f"Setup authenticator with: {secret}")
368
code = input("Enter verification code: ")
369
370
if u.verify_software_token(code):
371
u.set_user_mfa_preference(
372
sms_mfa=True,
373
software_token_mfa=True,
374
preferred="SOFTWARE_TOKEN"
375
)
376
print("Both MFA methods enabled (TOTP preferred)!")
377
378
elif choice == "4":
379
# Ensure TOTP is set up
380
secret = u.associate_software_token()
381
print(f"Setup authenticator with: {secret}")
382
code = input("Enter verification code: ")
383
384
if u.verify_software_token(code):
385
u.set_user_mfa_preference(
386
sms_mfa=True,
387
software_token_mfa=True,
388
preferred="SMS"
389
)
390
print("Both MFA methods enabled (SMS preferred)!")
391
392
elif choice == "5":
393
u.set_user_mfa_preference(
394
sms_mfa=False,
395
software_token_mfa=False
396
)
397
print("MFA disabled!")
398
399
else:
400
print("Invalid choice")
401
402
# Usage
403
u = Cognito('pool-id', 'client-id', username='user')
404
u.authenticate(password='password')
405
manage_mfa_settings(u)
406
```
407
408
### Production MFA Helper
409
410
```python
411
class MFAHelper:
412
"""Helper class for production MFA handling."""
413
414
def __init__(self, user_pool_id, client_id):
415
self.user_pool_id = user_pool_id
416
self.client_id = client_id
417
418
def authenticate_user(self, username, password, mfa_callback=None):
419
"""
420
Authenticate user with MFA callback support.
421
422
Args:
423
username: Username
424
password: Password
425
mfa_callback: Function to get MFA code (type, tokens) -> code
426
"""
427
u = Cognito(self.user_pool_id, self.client_id, username=username)
428
429
try:
430
u.authenticate(password=password)
431
return u, "SUCCESS"
432
433
except SoftwareTokenMFAChallengeException as e:
434
if mfa_callback:
435
code = mfa_callback("TOTP", e.get_tokens())
436
u.respond_to_software_token_mfa_challenge(code, e.get_tokens())
437
return u, "MFA_SUCCESS"
438
return None, "MFA_REQUIRED_TOTP"
439
440
except SMSMFAChallengeException as e:
441
if mfa_callback:
442
code = mfa_callback("SMS", e.get_tokens())
443
u.respond_to_sms_mfa_challenge(code, e.get_tokens())
444
return u, "MFA_SUCCESS"
445
return None, "MFA_REQUIRED_SMS"
446
447
except Exception as e:
448
return None, f"AUTH_FAILED: {e}"
449
450
def setup_totp(self, username, password, device_name="Device"):
451
"""Setup TOTP MFA for user."""
452
u = Cognito(self.user_pool_id, self.client_id, username=username)
453
u.authenticate(password=password)
454
455
secret = u.associate_software_token()
456
return secret, u
457
458
# Usage
459
def get_mfa_code(mfa_type, tokens):
460
"""Callback to get MFA code from user."""
461
if mfa_type == "TOTP":
462
return input("Enter TOTP code: ")
463
elif mfa_type == "SMS":
464
return input("Enter SMS code: ")
465
466
helper = MFAHelper('pool-id', 'client-id')
467
user, status = helper.authenticate_user('username', 'password', get_mfa_code)
468
469
if status == "SUCCESS":
470
print("Authenticated without MFA")
471
elif status == "MFA_SUCCESS":
472
print("Authenticated with MFA")
473
else:
474
print(f"Authentication failed: {status}")
475
```