or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mddevice-authentication.mdgroup-management.mdhttp-integration.mdindex.mdmfa.mdpassword-management.mdsrp-authentication.mduser-management.md

srp-authentication.mddocs/

0

# SRP Authentication Protocol

1

2

Low-level Secure Remote Password (SRP) protocol implementation for AWS Cognito User Pools. SRP provides cryptographically secure authentication without transmitting passwords over the network, forming the foundation for secure user authentication in pycognito.

3

4

## Capabilities

5

6

### AWSSRP Class

7

8

The core SRP implementation that handles the complete SRP authentication flow with AWS Cognito.

9

10

```python { .api }

11

class AWSSRP:

12

"""AWS SRP authentication protocol implementation."""

13

14

# Challenge type constants

15

SMS_MFA_CHALLENGE = "SMS_MFA"

16

SOFTWARE_TOKEN_MFA_CHALLENGE = "SOFTWARE_TOKEN_MFA"

17

NEW_PASSWORD_REQUIRED_CHALLENGE = "NEW_PASSWORD_REQUIRED"

18

PASSWORD_VERIFIER_CHALLENGE = "PASSWORD_VERIFIER"

19

DEVICE_SRP_CHALLENGE = "DEVICE_SRP_AUTH"

20

DEVICE_PASSWORD_VERIFIER_CHALLENGE = "DEVICE_PASSWORD_VERIFIER"

21

22

def __init__(self, username: str, password: str, pool_id: str, client_id: str,

23

pool_region: str = None, client=None, client_secret: str = None,

24

device_key: str = None, device_group_key: str = None,

25

device_password: str = None):

26

"""

27

Initialize AWSSRP authentication client.

28

29

Args:

30

username (str): User's username

31

password (str): User's password

32

pool_id (str): Cognito User Pool ID

33

client_id (str): Cognito Client ID

34

pool_region (str, optional): AWS region (extracted from pool_id if not provided)

35

client (boto3.client, optional): Pre-configured boto3 cognito-idp client

36

client_secret (str, optional): Client secret if app client requires it

37

device_key (str, optional): Device key for device authentication

38

device_group_key (str, optional): Device group key for device authentication

39

device_password (str, optional): Device password for device authentication

40

41

Raises:

42

ValueError: If pool_region and client are both specified, or if device parameters are partially provided

43

44

Note:

45

Device authentication requires all three device parameters or none at all.

46

"""

47

```

48

49

**Usage Example:**

50

51

```python

52

from pycognito.aws_srp import AWSSRP

53

54

# Basic SRP authentication

55

aws_srp = AWSSRP(

56

username='user@example.com',

57

password='user-password',

58

pool_id='us-east-1_example123',

59

client_id='your-client-id'

60

)

61

62

# With client secret

63

aws_srp = AWSSRP(

64

username='user@example.com',

65

password='user-password',

66

pool_id='us-east-1_example123',

67

client_id='your-client-id',

68

client_secret='your-client-secret'

69

)

70

71

# With device authentication

72

aws_srp = AWSSRP(

73

username='user@example.com',

74

password='user-password',

75

pool_id='us-east-1_example123',

76

client_id='your-client-id',

77

device_key='device-key-123',

78

device_group_key='device-group-456',

79

device_password='device-password-789'

80

)

81

```

82

83

### SRP Authentication Flow

84

85

Execute the complete SRP authentication flow with challenge handling.

86

87

```python { .api }

88

def authenticate_user(self, client=None, client_metadata: dict = None) -> dict:

89

"""

90

Authenticate user using SRP protocol.

91

92

Args:

93

client (boto3.client, optional): Override the default boto3 client

94

client_metadata (dict, optional): Custom workflow metadata

95

96

Returns:

97

dict: Authentication result with tokens

98

{

99

'AuthenticationResult': {

100

'AccessToken': str,

101

'IdToken': str,

102

'RefreshToken': str,

103

'TokenType': str,

104

'ExpiresIn': int

105

},

106

'ChallengeName': str, # If additional challenges required

107

'Session': str # Challenge session token

108

}

109

110

Raises:

111

ForceChangePasswordException: When password change is required

112

SoftwareTokenMFAChallengeException: When TOTP MFA is required

113

SMSMFAChallengeException: When SMS MFA is required

114

NotImplementedError: For unsupported challenge types

115

116

Flow:

117

1. Initiate SRP authentication with public key

118

2. Process password verifier challenge

119

3. Handle device challenges if device keys provided

120

4. Return tokens or raise MFA/password challenges

121

"""

122

```

123

124

**Usage Example:**

125

126

```python

127

from pycognito.exceptions import (

128

ForceChangePasswordException,

129

SoftwareTokenMFAChallengeException,

130

SMSMFAChallengeException

131

)

132

133

try:

134

# Perform SRP authentication

135

tokens = aws_srp.authenticate_user()

136

137

# Success - tokens contain access, ID, and refresh tokens

138

print("Authentication successful!")

139

print(f"Access Token: {tokens['AuthenticationResult']['AccessToken']}")

140

141

except ForceChangePasswordException:

142

print("Password change required")

143

# Handle new password challenge

144

145

except SoftwareTokenMFAChallengeException as e:

146

print("TOTP MFA required")

147

# Handle software token MFA

148

mfa_tokens = e.get_tokens()

149

150

except SMSMFAChallengeException as e:

151

print("SMS MFA required")

152

# Handle SMS MFA

153

mfa_tokens = e.get_tokens()

154

```

155

156

### New Password Challenge

157

158

Handle the new password required challenge when users must set a new password.

159

160

```python { .api }

161

def set_new_password_challenge(self, new_password: str, client=None) -> dict:

162

"""

163

Handle new password required challenge.

164

165

Args:

166

new_password (str): New password to set

167

client (boto3.client, optional): Override the default boto3 client

168

169

Returns:

170

dict: Authentication result after password is set

171

172

Use Cases:

173

- User logging in with temporary password (admin created users)

174

- Password expiration policies requiring new password

175

- Security policies forcing password updates

176

177

Flow:

178

1. Initiate SRP authentication

179

2. Process password verifier challenge

180

3. Handle NEW_PASSWORD_REQUIRED challenge with new password

181

4. Return final authentication tokens

182

"""

183

```

184

185

**Usage Example:**

186

187

```python

188

# Handle new password requirement

189

try:

190

tokens = aws_srp.authenticate_user()

191

except ForceChangePasswordException:

192

print("Setting new password...")

193

194

new_password = input("Enter new password: ")

195

tokens = aws_srp.set_new_password_challenge(new_password)

196

197

print("New password set successfully!")

198

print(f"Access Token: {tokens['AuthenticationResult']['AccessToken']}")

199

```

200

201

### Authentication Parameters

202

203

Generate authentication parameters for SRP initiation.

204

205

```python { .api }

206

def get_auth_params(self) -> dict:

207

"""

208

Get authentication parameters for SRP initiation.

209

210

Returns:

211

dict: Parameters for initiate_auth call

212

{

213

'USERNAME': str,

214

'SRP_A': str, # Public key for SRP

215

'SECRET_HASH': str, # If client secret is configured

216

'DEVICE_KEY': str # If device authentication enabled

217

}

218

219

Note:

220

These parameters are used internally but can be useful for

221

custom authentication flows or debugging.

222

"""

223

```

224

225

### Challenge Processing

226

227

Process SRP authentication challenges with password verification.

228

229

```python { .api }

230

def process_challenge(self, challenge_parameters: dict, request_parameters: dict) -> dict:

231

"""

232

Process password verifier challenge.

233

234

Args:

235

challenge_parameters (dict): Challenge data from AWS Cognito

236

request_parameters (dict): Original request parameters

237

238

Returns:

239

dict: Challenge response parameters

240

{

241

'TIMESTAMP': str,

242

'USERNAME': str,

243

'PASSWORD_CLAIM_SECRET_BLOCK': str,

244

'PASSWORD_CLAIM_SIGNATURE': str,

245

'SECRET_HASH': str, # If client secret configured

246

'DEVICE_KEY': str # If device authentication enabled

247

}

248

249

Internal Flow:

250

1. Extract challenge parameters (USER_ID_FOR_SRP, SALT, SRP_B, SECRET_BLOCK)

251

2. Compute password authentication key using SRP protocol

252

3. Generate HMAC signature with secret block and timestamp

253

4. Return challenge response for AWS verification

254

"""

255

256

def process_device_challenge(self, challenge_parameters: dict) -> dict:

257

"""

258

Process device password verifier challenge.

259

260

Args:

261

challenge_parameters (dict): Device challenge data from AWS Cognito

262

263

Returns:

264

dict: Device challenge response parameters

265

266

Note:

267

Similar to process_challenge but uses device-specific credentials

268

and authentication keys for device verification.

269

"""

270

```

271

272

### Cryptographic Utilities

273

274

Static methods for SRP cryptographic operations.

275

276

```python { .api }

277

@staticmethod

278

def get_secret_hash(username: str, client_id: str, client_secret: str) -> str:

279

"""

280

Calculate secret hash for client secret authentication.

281

282

Args:

283

username (str): Username

284

client_id (str): Cognito client ID

285

client_secret (str): Cognito client secret

286

287

Returns:

288

str: Base64-encoded HMAC-SHA256 hash

289

290

Formula:

291

HMAC-SHA256(client_secret, username + client_id)

292

"""

293

294

@staticmethod

295

def get_cognito_formatted_timestamp(input_datetime: datetime) -> str:

296

"""

297

Format datetime for Cognito authentication.

298

299

Args:

300

input_datetime (datetime): Datetime to format

301

302

Returns:

303

str: Formatted timestamp string (e.g., "Mon Jan 2 15:04:05 UTC 2006")

304

305

Note:

306

Uses specific format required by AWS Cognito SRP protocol.

307

"""

308

```

309

310

**Usage Example:**

311

312

```python

313

from datetime import datetime

314

315

# Calculate secret hash

316

secret_hash = AWSSRP.get_secret_hash(

317

username='user@example.com',

318

client_id='your-client-id',

319

client_secret='your-client-secret'

320

)

321

322

# Format timestamp for Cognito

323

timestamp = AWSSRP.get_cognito_formatted_timestamp(datetime.utcnow())

324

print(f"Cognito timestamp: {timestamp}")

325

```

326

327

## SRP Cryptographic Functions

328

329

Low-level cryptographic functions that implement the SRP protocol mathematics.

330

331

```python { .api }

332

def hash_sha256(buf: bytes) -> str:

333

"""Hash buffer using SHA256 and return hex digest."""

334

335

def hex_hash(hex_string: str) -> str:

336

"""Hash hex string using SHA256."""

337

338

def hex_to_long(hex_string: str) -> int:

339

"""Convert hex string to long integer."""

340

341

def long_to_hex(long_num: int) -> str:

342

"""Convert long integer to hex string."""

343

344

def get_random(nbytes: int) -> int:

345

"""Generate random long integer from n bytes."""

346

347

def pad_hex(long_int: int | str) -> str:

348

"""Pad hex string for consistent hashing."""

349

350

def compute_hkdf(ikm: bytes, salt: bytes) -> bytes:

351

"""Compute HKDF (HMAC-based Key Derivation Function)."""

352

353

def calculate_u(big_a: int, big_b: int) -> int:

354

"""Calculate SRP U value from client and server public keys."""

355

356

def generate_hash_device(device_group_key: str, device_key: str) -> tuple:

357

"""Generate device hash and verifier for device authentication."""

358

```

359

360

## Usage Patterns

361

362

### Direct SRP Authentication

363

364

```python

365

def direct_srp_authentication(username, password, pool_id, client_id):

366

"""Direct SRP authentication without Cognito wrapper."""

367

368

# Initialize SRP client

369

aws_srp = AWSSRP(

370

username=username,

371

password=password,

372

pool_id=pool_id,

373

client_id=client_id

374

)

375

376

try:

377

# Perform authentication

378

tokens = aws_srp.authenticate_user()

379

380

# Extract tokens

381

auth_result = tokens['AuthenticationResult']

382

access_token = auth_result['AccessToken']

383

id_token = auth_result['IdToken']

384

refresh_token = auth_result.get('RefreshToken')

385

386

print("SRP Authentication successful!")

387

return {

388

'access_token': access_token,

389

'id_token': id_token,

390

'refresh_token': refresh_token

391

}

392

393

except Exception as e:

394

print(f"SRP Authentication failed: {e}")

395

return None

396

397

# Usage

398

tokens = direct_srp_authentication(

399

'user@example.com',

400

'password',

401

'us-east-1_example123',

402

'your-client-id'

403

)

404

```

405

406

### Custom SRP Flow with Challenge Handling

407

408

```python

409

def custom_srp_flow_with_challenges(username, password, pool_id, client_id):

410

"""Custom SRP flow with manual challenge handling."""

411

412

aws_srp = AWSSRP(username, password, pool_id, client_id)

413

414

# Step 1: Get authentication parameters

415

auth_params = aws_srp.get_auth_params()

416

print(f"SRP_A (public key): {auth_params['SRP_A']}")

417

418

# Step 2: Initiate auth with AWS

419

response = aws_srp.client.initiate_auth(

420

AuthFlow='USER_SRP_AUTH',

421

AuthParameters=auth_params,

422

ClientId=client_id

423

)

424

425

# Step 3: Handle password verifier challenge

426

if response['ChallengeName'] == 'PASSWORD_VERIFIER':

427

print("Processing password verifier challenge...")

428

429

# Process challenge

430

challenge_response = aws_srp.process_challenge(

431

response['ChallengeParameters'],

432

auth_params

433

)

434

435

# Respond to challenge

436

tokens = aws_srp.client.respond_to_auth_challenge(

437

ClientId=client_id,

438

ChallengeName='PASSWORD_VERIFIER',

439

ChallengeResponses=challenge_response

440

)

441

442

# Check for additional challenges

443

if 'ChallengeName' in tokens:

444

print(f"Additional challenge required: {tokens['ChallengeName']}")

445

return tokens # Return for further processing

446

else:

447

print("Authentication completed!")

448

return tokens['AuthenticationResult']

449

450

else:

451

print(f"Unexpected challenge: {response['ChallengeName']}")

452

return None

453

454

# Usage

455

result = custom_srp_flow_with_challenges(

456

'user@example.com',

457

'password',

458

'us-east-1_example123',

459

'your-client-id'

460

)

461

```

462

463

### SRP with Custom Boto3 Configuration

464

465

```python

466

def srp_with_custom_boto3():

467

"""SRP authentication with custom boto3 configuration."""

468

469

import boto3

470

from botocore.config import Config

471

472

# Custom boto3 configuration

473

config = Config(

474

region_name='us-east-1',

475

retries={'max_attempts': 3},

476

read_timeout=30,

477

connect_timeout=10

478

)

479

480

# Create custom cognito client

481

cognito_client = boto3.client('cognito-idp', config=config)

482

483

# Initialize SRP with custom client

484

aws_srp = AWSSRP(

485

username='user@example.com',

486

password='password',

487

pool_id='us-east-1_example123',

488

client_id='your-client-id',

489

client=cognito_client # Use custom client

490

)

491

492

# Authenticate

493

tokens = aws_srp.authenticate_user()

494

return tokens

495

496

# Usage

497

tokens = srp_with_custom_boto3()

498

```

499

500

### SRP Protocol Mathematics Example

501

502

```python

503

def demonstrate_srp_mathematics():

504

"""Demonstrate the mathematical operations in SRP protocol."""

505

506

from pycognito.aws_srp import (

507

hex_to_long, long_to_hex, pad_hex,

508

calculate_u, compute_hkdf, N_HEX, G_HEX

509

)

510

511

# SRP protocol constants

512

N = hex_to_long(N_HEX) # Large prime number

513

g = hex_to_long(G_HEX) # Generator (2)

514

515

print(f"SRP Prime N: {N}")

516

print(f"SRP Generator g: {g}")

517

518

# Simulate SRP key generation

519

a = 12345 # Client private key (normally random)

520

A = pow(g, a, N) # Client public key: g^a mod N

521

522

print(f"Client private key a: {a}")

523

print(f"Client public key A: {A}")

524

print(f"A (hex): {long_to_hex(A)}")

525

print(f"A (padded): {pad_hex(A)}")

526

527

# Simulate server public key

528

B = 67890 # Server public key (normally calculated)

529

530

# Calculate U value (hash of A and B)

531

u = calculate_u(A, B)

532

print(f"U value: {u}")

533

534

# Demonstrate HKDF

535

ikm = b"input key material"

536

salt = b"salt value"

537

derived_key = compute_hkdf(ikm, salt)

538

print(f"HKDF result: {derived_key.hex()}")

539

540

# Usage

541

demonstrate_srp_mathematics()

542

```

543

544

### Production SRP Wrapper

545

546

```python

547

class ProductionSRPClient:

548

"""Production-ready SRP client with error handling and logging."""

549

550

def __init__(self, pool_id, client_id, client_secret=None):

551

self.pool_id = pool_id

552

self.client_id = client_id

553

self.client_secret = client_secret

554

555

def authenticate(self, username, password, retry_count=3):

556

"""Authenticate with retry logic and comprehensive error handling."""

557

558

for attempt in range(retry_count):

559

try:

560

print(f"Authentication attempt {attempt + 1}")

561

562

aws_srp = AWSSRP(

563

username=username,

564

password=password,

565

pool_id=self.pool_id,

566

client_id=self.client_id,

567

client_secret=self.client_secret

568

)

569

570

tokens = aws_srp.authenticate_user()

571

572

print("Authentication successful!")

573

return {

574

'success': True,

575

'tokens': tokens,

576

'attempt': attempt + 1

577

}

578

579

except ForceChangePasswordException:

580

return {

581

'success': False,

582

'error': 'PASSWORD_CHANGE_REQUIRED',

583

'message': 'User must change password'

584

}

585

586

except SoftwareTokenMFAChallengeException as e:

587

return {

588

'success': False,

589

'error': 'MFA_REQUIRED_TOTP',

590

'mfa_tokens': e.get_tokens()

591

}

592

593

except SMSMFAChallengeException as e:

594

return {

595

'success': False,

596

'error': 'MFA_REQUIRED_SMS',

597

'mfa_tokens': e.get_tokens()

598

}

599

600

except Exception as e:

601

print(f"Attempt {attempt + 1} failed: {e}")

602

if attempt == retry_count - 1:

603

return {

604

'success': False,

605

'error': 'AUTHENTICATION_FAILED',

606

'message': str(e)

607

}

608

609

return {

610

'success': False,

611

'error': 'MAX_RETRIES_EXCEEDED'

612

}

613

614

# Usage

615

srp_client = ProductionSRPClient(

616

pool_id='us-east-1_example123',

617

client_id='your-client-id',

618

client_secret='your-client-secret'

619

)

620

621

result = srp_client.authenticate('user@example.com', 'password')

622

623

if result['success']:

624

print("Authentication successful!")

625

tokens = result['tokens']

626

else:

627

print(f"Authentication failed: {result['error']}")

628

if 'mfa_tokens' in result:

629

print("MFA required - handle accordingly")

630

```