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
```