or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

common-auth-flows.mdconfidential-client.mdindex.mdmanaged-identity.mdpublic-client.mdsecurity-advanced.mdtoken-cache.md

common-auth-flows.mddocs/

0

# Common Authentication Flows

1

2

MSAL Python provides shared authentication functionality across both public and confidential client applications. These common flows include silent token acquisition from cache, authorization code flow for web scenarios, username/password authentication, refresh token handling, and comprehensive account management.

3

4

## Capabilities

5

6

### Silent Token Acquisition

7

8

The primary method for acquiring tokens without user interaction by leveraging cached tokens and automatic refresh capabilities.

9

10

```python { .api }

11

def acquire_token_silent(

12

self,

13

scopes: list,

14

account,

15

authority=None,

16

force_refresh=False,

17

claims_challenge=None,

18

**kwargs

19

):

20

"""

21

Acquire access token silently using cached refresh token.

22

23

Parameters:

24

- scopes: List of scopes to request

25

- account: Account object from get_accounts() or cache

26

- authority: Authority URL override

27

- force_refresh: Force token refresh even if cached token is valid

28

- claims_challenge: Additional claims from resource provider

29

30

Returns:

31

Dictionary with 'access_token' on success, 'error' on failure

32

"""

33

34

def acquire_token_silent_with_error(

35

self,

36

scopes: list,

37

account,

38

authority=None,

39

force_refresh=False,

40

claims_challenge=None,

41

**kwargs

42

):

43

"""

44

Same as acquire_token_silent but returns detailed error information.

45

46

Returns:

47

Dictionary with detailed error information on failure

48

"""

49

```

50

51

Usage example:

52

53

```python

54

import msal

55

56

app = msal.PublicClientApplication(

57

client_id="your-client-id",

58

authority="https://login.microsoftonline.com/common"

59

)

60

61

# Get cached accounts

62

accounts = app.get_accounts()

63

64

if accounts:

65

# Try silent acquisition first

66

result = app.acquire_token_silent(

67

scopes=["User.Read", "Mail.Read"],

68

account=accounts[0]

69

)

70

71

if "access_token" in result:

72

print("Silent authentication successful!")

73

access_token = result["access_token"]

74

else:

75

print(f"Silent authentication failed: {result.get('error')}")

76

if result.get('error') == 'interaction_required':

77

# Fall back to interactive authentication

78

result = app.acquire_token_interactive(

79

scopes=["User.Read", "Mail.Read"]

80

)

81

else:

82

print("No cached accounts found")

83

# Perform initial interactive authentication

84

result = app.acquire_token_interactive(scopes=["User.Read", "Mail.Read"])

85

86

# Force refresh example

87

result = app.acquire_token_silent(

88

scopes=["User.Read"],

89

account=accounts[0],

90

force_refresh=True # Always get fresh token

91

)

92

```

93

94

### Authorization Code Flow

95

96

OAuth2 authorization code flow implementation with PKCE (Proof Key for Code Exchange) support for enhanced security.

97

98

```python { .api }

99

def initiate_auth_code_flow(

100

self,

101

scopes: list,

102

redirect_uri=None,

103

state=None,

104

prompt=None,

105

login_hint=None,

106

domain_hint=None,

107

claims_challenge=None,

108

max_age=None,

109

**kwargs

110

):

111

"""

112

Initiate authorization code flow.

113

114

Parameters:

115

- scopes: List of scopes to request

116

- redirect_uri: Redirect URI registered in Azure portal

117

- state: State parameter for CSRF protection

118

- prompt: Prompt behavior (none, login, consent, select_account)

119

- login_hint: Email to pre-populate sign-in form

120

- domain_hint: Domain hint to skip domain selection

121

- claims_challenge: Additional claims from resource provider

122

- max_age: Maximum authentication age in seconds

123

124

Returns:

125

Dictionary containing auth_uri, state, code_verifier, and other flow data

126

"""

127

128

def acquire_token_by_auth_code_flow(

129

self,

130

auth_code_flow: dict,

131

auth_response: dict,

132

scopes=None,

133

**kwargs

134

):

135

"""

136

Complete authorization code flow.

137

138

Parameters:

139

- auth_code_flow: Flow state from initiate_auth_code_flow()

140

- auth_response: Authorization response parameters from redirect

141

- scopes: Optional scopes override

142

143

Returns:

144

Dictionary with 'access_token' on success, 'error' on failure

145

"""

146

147

def get_authorization_request_url(

148

self,

149

scopes: list,

150

redirect_uri=None,

151

state=None,

152

prompt=None,

153

login_hint=None,

154

domain_hint=None,

155

claims_challenge=None,

156

max_age=None,

157

**kwargs

158

):

159

"""

160

Generate authorization URL (simpler alternative to initiate_auth_code_flow).

161

162

Returns:

163

Authorization URL string

164

"""

165

```

166

167

Usage example for web application:

168

169

```python

170

import msal

171

from flask import Flask, request, redirect, session, url_for

172

import secrets

173

174

app_flask = Flask(__name__)

175

app_flask.secret_key = secrets.token_hex(16)

176

177

msal_app = msal.ConfidentialClientApplication(

178

client_id="your-client-id",

179

client_credential="your-client-secret",

180

authority="https://login.microsoftonline.com/common"

181

)

182

183

@app_flask.route('/login')

184

def login():

185

# Generate CSRF state

186

state = secrets.token_urlsafe(32)

187

session['state'] = state

188

189

# Initiate auth code flow

190

auth_flow = msal_app.initiate_auth_code_flow(

191

scopes=["User.Read", "profile", "openid"],

192

redirect_uri=url_for('auth_response', _external=True),

193

state=state

194

)

195

196

# Store flow data in session

197

session['auth_flow'] = auth_flow

198

199

# Redirect to authorization URL

200

return redirect(auth_flow['auth_uri'])

201

202

@app_flask.route('/auth-response')

203

def auth_response():

204

# Verify state parameter

205

if request.args.get('state') != session.get('state'):

206

return "Invalid state parameter", 400

207

208

# Get stored flow data

209

auth_flow = session.get('auth_flow', {})

210

211

# Handle authorization errors

212

if 'error' in request.args:

213

error = request.args.get('error')

214

error_description = request.args.get('error_description', '')

215

return f"Authorization error: {error} - {error_description}", 400

216

217

# Complete the flow

218

result = msal_app.acquire_token_by_auth_code_flow(

219

auth_code_flow=auth_flow,

220

auth_response=request.args

221

)

222

223

if "access_token" in result:

224

# Store tokens in session (consider more secure storage in production)

225

session['tokens'] = {

226

'access_token': result['access_token'],

227

'expires_in': result.get('expires_in'),

228

'refresh_token': result.get('refresh_token'),

229

'id_token_claims': result.get('id_token_claims', {})

230

}

231

232

username = result.get('id_token_claims', {}).get('preferred_username', 'Unknown')

233

return f"Login successful! Welcome, {username}"

234

else:

235

return f"Login failed: {result.get('error_description')}", 400

236

237

@app_flask.route('/logout')

238

def logout():

239

# Clear session

240

session.clear()

241

242

# Optional: redirect to logout URL

243

logout_url = f"https://login.microsoftonline.com/common/oauth2/v2.0/logout"

244

return redirect(logout_url)

245

```

246

247

### Username/Password Authentication

248

249

Resource Owner Password Credentials (ROPC) flow for scenarios where interactive authentication is not possible. Note: This flow is not recommended for most scenarios due to security limitations.

250

251

```python { .api }

252

def acquire_token_by_username_password(

253

self,

254

username: str,

255

password: str,

256

scopes: list,

257

claims_challenge=None,

258

auth_scheme=None,

259

**kwargs

260

):

261

"""

262

Acquire token using username and password.

263

264

Parameters:

265

- username: User's email address or UPN

266

- password: User's password

267

- scopes: List of scopes to request

268

- claims_challenge: Additional claims from resource provider

269

- auth_scheme: Authentication scheme (e.g., PopAuthScheme for PoP tokens)

270

271

Returns:

272

Dictionary with 'access_token' on success, 'error' on failure

273

"""

274

```

275

276

Usage example:

277

278

```python

279

import msal

280

import getpass

281

282

app = msal.PublicClientApplication(

283

client_id="your-client-id",

284

authority="https://login.microsoftonline.com/your-tenant-id"

285

)

286

287

# Get credentials (use secure input for password)

288

username = input("Username: ")

289

password = getpass.getpass("Password: ")

290

291

result = app.acquire_token_by_username_password(

292

username=username,

293

password=password,

294

scopes=["User.Read"]

295

)

296

297

if "access_token" in result:

298

print("Username/password authentication successful!")

299

access_token = result["access_token"]

300

else:

301

print(f"Authentication failed: {result.get('error_description')}")

302

if result.get('error') == 'invalid_grant':

303

print("Invalid username or password")

304

elif result.get('error') == 'interaction_required':

305

print("Multi-factor authentication required - use interactive flow")

306

```

307

308

### Refresh Token Handling

309

310

Direct refresh token usage for scenarios where you have a refresh token from external sources.

311

312

```python { .api }

313

def acquire_token_by_refresh_token(

314

self,

315

refresh_token: str,

316

scopes: list,

317

**kwargs

318

):

319

"""

320

Acquire token using refresh token.

321

322

Parameters:

323

- refresh_token: Refresh token string

324

- scopes: List of scopes to request

325

326

Returns:

327

Dictionary with 'access_token' on success, 'error' on failure

328

"""

329

```

330

331

Usage example:

332

333

```python

334

import msal

335

336

app = msal.PublicClientApplication(

337

client_id="your-client-id",

338

authority="https://login.microsoftonline.com/common"

339

)

340

341

# Use refresh token obtained from elsewhere

342

refresh_token = "0.AAAA..." # Your refresh token

343

344

result = app.acquire_token_by_refresh_token(

345

refresh_token=refresh_token,

346

scopes=["User.Read", "Mail.Read"]

347

)

348

349

if "access_token" in result:

350

print("Refresh token authentication successful!")

351

access_token = result["access_token"]

352

new_refresh_token = result.get("refresh_token") # May get new refresh token

353

else:

354

print(f"Refresh failed: {result.get('error_description')}")

355

if result.get('error') == 'invalid_grant':

356

print("Refresh token expired or invalid")

357

```

358

359

### Account Management

360

361

Comprehensive account management including listing cached accounts and removing accounts from cache.

362

363

```python { .api }

364

def get_accounts(self, username=None) -> list:

365

"""

366

Get list of accounts in cache.

367

368

Parameters:

369

- username: Filter accounts by username

370

371

Returns:

372

List of account dictionaries

373

"""

374

375

def remove_account(self, account):

376

"""

377

Remove account and associated tokens from cache.

378

379

Parameters:

380

- account: Account object from get_accounts()

381

"""

382

383

def is_pop_supported(self) -> bool:

384

"""

385

Check if Proof-of-Possession tokens are supported.

386

387

Returns:

388

True if PoP tokens are supported (requires broker)

389

"""

390

```

391

392

Usage example:

393

394

```python

395

import msal

396

397

app = msal.PublicClientApplication(

398

client_id="your-client-id",

399

authority="https://login.microsoftonline.com/common"

400

)

401

402

# List all cached accounts

403

accounts = app.get_accounts()

404

print(f"Found {len(accounts)} cached accounts:")

405

406

for i, account in enumerate(accounts):

407

print(f"{i+1}. {account.get('username')}")

408

print(f" Environment: {account.get('environment')}")

409

print(f" Home Account ID: {account.get('home_account_id')}")

410

print(f" Authority Type: {account.get('authority_type')}")

411

412

# Filter accounts by username

413

specific_accounts = app.get_accounts(username="user@example.com")

414

print(f"Accounts for user@example.com: {len(specific_accounts)}")

415

416

# Remove specific account

417

if accounts:

418

print(f"Removing account: {accounts[0].get('username')}")

419

app.remove_account(accounts[0])

420

421

# Verify removal

422

remaining_accounts = app.get_accounts()

423

print(f"Remaining accounts: {len(remaining_accounts)}")

424

425

# Account-specific silent authentication

426

for account in app.get_accounts():

427

result = app.acquire_token_silent(

428

scopes=["User.Read"],

429

account=account

430

)

431

432

if "access_token" in result:

433

print(f"Successfully acquired token for {account.get('username')}")

434

else:

435

print(f"Failed to acquire token for {account.get('username')}: {result.get('error')}")

436

```

437

438

### Legacy Authorization Code Method

439

440

Simplified authorization code method (legacy - use auth code flow methods for new applications):

441

442

```python { .api }

443

def acquire_token_by_authorization_code(

444

self,

445

code: str,

446

redirect_uri=None,

447

scopes=None,

448

**kwargs

449

):

450

"""

451

Acquire token using authorization code (legacy method).

452

453

Parameters:

454

- code: Authorization code from redirect

455

- redirect_uri: Redirect URI used in authorization request

456

- scopes: List of scopes

457

458

Returns:

459

Dictionary with 'access_token' on success, 'error' on failure

460

"""

461

```

462

463

## Error Handling

464

465

Common error scenarios and handling patterns for shared authentication flows:

466

467

```python

468

# Silent authentication error handling

469

result = app.acquire_token_silent(scopes=["User.Read"], account=account)

470

471

if "access_token" not in result:

472

error = result.get("error")

473

474

if error == "interaction_required":

475

# User interaction needed (MFA, consent, etc.)

476

print("Interactive authentication required")

477

result = app.acquire_token_interactive(scopes=["User.Read"])

478

elif error == "invalid_grant":

479

# Refresh token expired

480

print("Refresh token expired, removing account")

481

app.remove_account(account)

482

elif error == "token_expired":

483

# Access token expired (should auto-refresh)

484

print("Token expired, trying force refresh")

485

result = app.acquire_token_silent(

486

scopes=["User.Read"],

487

account=account,

488

force_refresh=True

489

)

490

else:

491

print(f"Silent authentication failed: {result.get('error_description')}")

492

493

# Authorization code flow error handling

494

auth_response = request.args # From web framework

495

496

if 'error' in auth_response:

497

error = auth_response.get('error')

498

if error == 'access_denied':

499

# User cancelled or denied consent

500

print("User denied consent")

501

elif error == 'invalid_scope':

502

# Invalid scope requested

503

print("Invalid scope in request")

504

else:

505

print(f"Authorization error: {auth_response.get('error_description')}")

506

else:

507

# Complete the flow

508

result = app.acquire_token_by_auth_code_flow(auth_flow, auth_response)

509

510

# Username/password error handling

511

result = app.acquire_token_by_username_password(username, password, scopes)

512

513

if "access_token" not in result:

514

error = result.get("error")

515

516

if error == "invalid_grant":

517

# Wrong credentials

518

print("Invalid username or password")

519

elif error == "interaction_required":

520

# MFA or other interaction needed

521

print("Multi-factor authentication required")

522

elif error == "invalid_client":

523

# ROPC not enabled for application

524

print("Username/password flow not enabled for this application")

525

```