0
# Confidential Client Applications
1
2
Confidential client applications are designed for server-side applications that can securely store credentials. MSAL Python's `ConfidentialClientApplication` supports client credentials flow for service-to-service authentication, authorization code flow for web applications, and on-behalf-of flow for middle-tier services.
3
4
## Capabilities
5
6
### Application Initialization
7
8
Creates a confidential client application with various credential types including client secrets, X.509 certificates, and Subject Name/Issuer authentication for certificate auto-rotation scenarios.
9
10
```python { .api }
11
class ConfidentialClientApplication(ClientApplication):
12
def __init__(
13
self,
14
client_id: str,
15
client_credential,
16
authority=None,
17
validate_authority=True,
18
token_cache=None,
19
http_client=None,
20
verify=True,
21
proxies=None,
22
timeout=None,
23
client_claims=None,
24
app_name=None,
25
app_version=None,
26
client_capabilities=None,
27
azure_region=None,
28
exclude_scopes=None,
29
http_cache=None,
30
instance_discovery=None,
31
enable_pii_log=None,
32
oidc_authority=None,
33
**kwargs
34
):
35
"""
36
Create a confidential client application.
37
38
Parameters:
39
- client_id: Your app's client ID from Azure portal
40
- client_credential: Client secret string, certificate dict, or assertion callable
41
- authority: Authority URL (default: https://login.microsoftonline.com/common)
42
- azure_region: Azure region for regional STS endpoints
43
- token_cache: Custom token cache instance
44
- http_client: Custom HTTP client
45
- proxies: HTTP proxy configuration
46
- timeout: HTTP timeout in seconds
47
"""
48
```
49
50
### Client Credential Types
51
52
#### Client Secret
53
```python
54
app = msal.ConfidentialClientApplication(
55
client_id="your-client-id",
56
client_credential="your-client-secret",
57
authority="https://login.microsoftonline.com/your-tenant-id"
58
)
59
```
60
61
#### X.509 Certificate
62
```python
63
client_credential = {
64
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
65
"thumbprint": "A1B2C3D4E5F6...",
66
"passphrase": "optional-passphrase" # If private key is encrypted
67
}
68
69
app = msal.ConfidentialClientApplication(
70
client_id="your-client-id",
71
client_credential=client_credential,
72
authority="https://login.microsoftonline.com/your-tenant-id"
73
)
74
```
75
76
#### Subject Name/Issuer Authentication (Certificate Auto-rotation)
77
```python
78
client_credential = {
79
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
80
"thumbprint": "A1B2C3D4E5F6...",
81
"public_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
82
"passphrase": "optional-passphrase"
83
}
84
85
app = msal.ConfidentialClientApplication(
86
client_id="your-client-id",
87
client_credential=client_credential,
88
authority="https://login.microsoftonline.com/your-tenant-id"
89
)
90
```
91
92
### Client Credentials Flow
93
94
Acquires tokens for the application itself (not on behalf of a user) using client credentials. Commonly used for daemon applications and service-to-service authentication.
95
96
```python { .api }
97
def acquire_token_for_client(
98
self,
99
scopes: list,
100
claims_challenge=None,
101
**kwargs
102
):
103
"""
104
Acquire token for the client application.
105
106
Parameters:
107
- scopes: List of scopes (typically ["{resource}/.default"])
108
- claims_challenge: Additional claims from resource provider
109
110
Returns:
111
Dictionary with 'access_token' on success, 'error' on failure
112
"""
113
114
def remove_tokens_for_client(self):
115
"""
116
Remove all tokens previously acquired via acquire_token_for_client().
117
"""
118
```
119
120
Usage example:
121
122
```python
123
import msal
124
125
app = msal.ConfidentialClientApplication(
126
client_id="your-client-id",
127
client_credential="your-client-secret",
128
authority="https://login.microsoftonline.com/your-tenant-id"
129
)
130
131
# Acquire token for Microsoft Graph
132
result = app.acquire_token_for_client(
133
scopes=["https://graph.microsoft.com/.default"]
134
)
135
136
if "access_token" in result:
137
print("Client credentials authentication successful!")
138
access_token = result["access_token"]
139
140
# Use the token to call Microsoft Graph API
141
import requests
142
headers = {"Authorization": f"Bearer {access_token}"}
143
response = requests.get("https://graph.microsoft.com/v1.0/users", headers=headers)
144
145
if response.status_code == 200:
146
users = response.json()
147
print(f"Found {len(users.get('value', []))} users")
148
else:
149
print(f"Authentication failed: {result.get('error_description')}")
150
151
# Clear client tokens when needed
152
app.remove_tokens_for_client()
153
```
154
155
### On-Behalf-Of Flow
156
157
Allows middle-tier services to acquire tokens on behalf of users. The service uses a user's access token to request additional tokens for downstream APIs.
158
159
```python { .api }
160
def acquire_token_on_behalf_of(
161
self,
162
user_assertion: str,
163
scopes: list,
164
claims_challenge=None,
165
**kwargs
166
):
167
"""
168
Acquire token on behalf of user.
169
170
Parameters:
171
- user_assertion: The user's access token received by the service
172
- scopes: List of scopes for downstream API
173
- claims_challenge: Additional claims from resource provider
174
175
Returns:
176
Dictionary with 'access_token' on success, 'error' on failure
177
"""
178
```
179
180
Usage example:
181
182
```python
183
import msal
184
from flask import Flask, request
185
186
app_flask = Flask(__name__)
187
188
# Configure MSAL
189
msal_app = msal.ConfidentialClientApplication(
190
client_id="your-service-client-id",
191
client_credential="your-service-client-secret",
192
authority="https://login.microsoftonline.com/your-tenant-id"
193
)
194
195
@app_flask.route('/api/data')
196
def get_data():
197
# Extract user's access token from Authorization header
198
auth_header = request.headers.get('Authorization', '')
199
if not auth_header.startswith('Bearer '):
200
return {"error": "Missing or invalid authorization header"}, 401
201
202
user_token = auth_header[7:] # Remove 'Bearer ' prefix
203
204
# Use OBO flow to get token for downstream API
205
result = msal_app.acquire_token_on_behalf_of(
206
user_assertion=user_token,
207
scopes=["https://api.downstream.com/.default"]
208
)
209
210
if "access_token" in result:
211
# Call downstream API with new token
212
import requests
213
headers = {"Authorization": f"Bearer {result['access_token']}"}
214
response = requests.get("https://api.downstream.com/data", headers=headers)
215
return response.json()
216
else:
217
return {"error": result.get("error_description")}, 400
218
```
219
220
### Authorization Code Flow for Web Apps
221
222
Handles the authorization code flow for web applications, including PKCE (Proof Key for Code Exchange) support for enhanced security.
223
224
The authorization code flow is inherited from the base `ClientApplication` class:
225
226
```python { .api }
227
def initiate_auth_code_flow(
228
self,
229
scopes: list,
230
redirect_uri=None,
231
state=None,
232
prompt=None,
233
login_hint=None,
234
domain_hint=None,
235
claims_challenge=None,
236
max_age=None,
237
**kwargs
238
):
239
"""
240
Initiate authorization code flow.
241
242
Returns:
243
Dictionary containing auth_uri and state information
244
"""
245
246
def acquire_token_by_auth_code_flow(
247
self,
248
auth_code_flow: dict,
249
auth_response: dict,
250
scopes=None,
251
**kwargs
252
):
253
"""
254
Complete authorization code flow.
255
256
Parameters:
257
- auth_code_flow: Flow state from initiate_auth_code_flow()
258
- auth_response: Authorization response from redirect URI
259
- scopes: Optional scopes override
260
261
Returns:
262
Dictionary with 'access_token' on success, 'error' on failure
263
"""
264
```
265
266
Usage example for web application:
267
268
```python
269
import msal
270
from flask import Flask, request, redirect, session, url_for
271
272
app_flask = Flask(__name__)
273
app_flask.secret_key = 'your-secret-key'
274
275
msal_app = msal.ConfidentialClientApplication(
276
client_id="your-webapp-client-id",
277
client_credential="your-webapp-client-secret",
278
authority="https://login.microsoftonline.com/your-tenant-id"
279
)
280
281
@app_flask.route('/login')
282
def login():
283
# Initiate auth code flow
284
auth_flow = msal_app.initiate_auth_code_flow(
285
scopes=["User.Read"],
286
redirect_uri=url_for('auth_response', _external=True)
287
)
288
289
# Store flow state in session
290
session['auth_flow'] = auth_flow
291
292
# Redirect user to authorization URL
293
return redirect(auth_flow['auth_uri'])
294
295
@app_flask.route('/auth-response')
296
def auth_response():
297
# Get stored flow state
298
auth_flow = session.get('auth_flow', {})
299
300
# Complete the flow with authorization response
301
result = msal_app.acquire_token_by_auth_code_flow(
302
auth_code_flow=auth_flow,
303
auth_response=request.args
304
)
305
306
if "access_token" in result:
307
# Store tokens in session
308
session['tokens'] = result
309
return "Login successful!"
310
else:
311
return f"Login failed: {result.get('error_description')}"
312
```
313
314
### Azure Regional Endpoints
315
316
For applications deployed in Azure, use regional endpoints for improved performance and compliance:
317
318
```python
319
app = msal.ConfidentialClientApplication(
320
client_id="your-client-id",
321
client_credential="your-client-secret",
322
authority="https://login.microsoftonline.com/your-tenant-id",
323
azure_region="eastus" # Specify Azure region
324
)
325
326
# Or use auto-detection in Azure environment
327
app = msal.ConfidentialClientApplication(
328
client_id="your-client-id",
329
client_credential="your-client-secret",
330
authority="https://login.microsoftonline.com/your-tenant-id",
331
azure_region=msal.ClientApplication.ATTEMPT_REGION_DISCOVERY
332
)
333
```
334
335
## Error Handling
336
337
Common error scenarios and handling patterns:
338
339
```python
340
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
341
342
if "access_token" in result:
343
# Success
344
access_token = result["access_token"]
345
expires_in = result["expires_in"]
346
elif result.get("error") == "invalid_client":
347
# Invalid client credentials
348
print("Invalid client ID or secret")
349
elif result.get("error") == "invalid_scope":
350
# Invalid or unauthorized scope
351
print(f"Invalid scope: {result.get('error_description')}")
352
elif result.get("error") == "unauthorized_client":
353
# Client not authorized for requested grant type
354
print("Client not authorized for client credentials flow")
355
else:
356
# Other error
357
print(f"Authentication failed: {result.get('error_description')}")
358
359
# Handle OBO-specific errors
360
obo_result = app.acquire_token_on_behalf_of(
361
user_assertion=user_token,
362
scopes=["https://api.downstream.com/.default"]
363
)
364
365
if obo_result.get("error") == "invalid_grant":
366
# User assertion is invalid or expired
367
print("User token is invalid or expired")
368
elif obo_result.get("error") == "consent_required":
369
# Additional consent needed
370
print("Additional consent required for downstream API")
371
```