0
# Token Caching
1
2
Thread-safe token storage and management with serialization capabilities for persistent caching across application sessions. The TokenCache class provides efficient token lookup, storage, removal, and automatic cleanup functionality.
3
4
## Capabilities
5
6
### TokenCache Constructor
7
8
Creates a new token cache instance with optional deserialization from previously saved state.
9
10
```python { .api }
11
def __init__(self, state=None):
12
"""
13
Initialize token cache.
14
15
Parameters:
16
- state (str, optional): Serialized cache state from previous session
17
"""
18
```
19
20
**Usage Example:**
21
22
```python
23
import adal
24
25
# Create empty cache
26
cache = adal.TokenCache()
27
28
# Create cache from previously saved state
29
with open('token_cache.json', 'r') as f:
30
cache_state = f.read()
31
cache = adal.TokenCache(state=cache_state)
32
33
# Use cache with authentication context
34
context = adal.AuthenticationContext(
35
authority='https://login.microsoftonline.com/tenant-id',
36
cache=cache
37
)
38
```
39
40
### Token Lookup
41
42
Searches the cache for tokens matching specified criteria. Returns a list of matching token entries.
43
44
```python { .api }
45
def find(self, query):
46
"""
47
Find tokens in cache matching query criteria.
48
49
Parameters:
50
- query (dict): Search criteria with optional keys:
51
- '_clientId' (str): OAuth client ID (note the underscore prefix)
52
- 'userId' (str): User identifier
53
- 'isMRRT' (bool): Whether to match multi-resource refresh tokens
54
55
Returns:
56
list: List of matching authentication result dictionaries
57
58
Note: Any parameter set to None acts as a wildcard (matches all).
59
All specified criteria must match (AND logic).
60
"""
61
```
62
63
**Usage Example:**
64
65
```python
66
# Find all tokens for a specific user
67
user_tokens = cache.find({'userId': 'user@tenant.com'})
68
69
# Find tokens for specific client and user
70
app_tokens = cache.find({
71
'_clientId': 'your-client-id',
72
'userId': 'user@tenant.com'
73
})
74
75
# Find multi-resource refresh tokens for a user
76
mrrt_tokens = cache.find({
77
'userId': 'user@tenant.com',
78
'isMRRT': True
79
})
80
81
# Find all cached tokens
82
all_tokens = cache.find({})
83
84
for token in user_tokens:
85
print(f"Resource: {token.get('resource')}")
86
print(f"Expires: {token.get('expiresOn')}")
87
```
88
89
### Token Storage
90
91
Adds new token entries to the cache. Automatically handles deduplication and updates existing entries.
92
93
```python { .api }
94
def add(self, entries):
95
"""
96
Add token entries to cache.
97
98
Parameters:
99
- entries (list or dict): Single token entry or list of token entries to add
100
Each entry should be an authentication result dictionary
101
"""
102
```
103
104
**Usage Example:**
105
106
```python
107
# Tokens are typically added automatically by AuthenticationContext
108
# But you can add tokens manually if needed
109
110
token_entry = {
111
'accessToken': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...',
112
'refreshToken': 'AQABAAAAAAD--DLA3VO7QrddgJg7WevrAg...',
113
'tokenType': 'Bearer',
114
'expiresIn': 3600,
115
'expiresOn': '2023-10-01T12:00:00Z',
116
'resource': 'https://management.azure.com/',
117
'userId': 'user@tenant.com',
118
'clientId': 'your-client-id'
119
}
120
121
cache.add(token_entry)
122
print(f"Cache has changes: {cache.has_state_changed}")
123
```
124
125
### Token Removal
126
127
Removes token entries from the cache based on matching criteria.
128
129
```python { .api }
130
def remove(self, entries):
131
"""
132
Remove token entries from cache.
133
134
Parameters:
135
- entries (list or dict): Single token entry or list of token entries to remove
136
Entries are matched based on authority, resource, client ID, and user ID
137
"""
138
```
139
140
**Usage Example:**
141
142
```python
143
# Remove specific tokens (typically done automatically on expiration)
144
expired_tokens = cache.find({'userId': 'user@tenant.com'})
145
cache.remove(expired_tokens)
146
147
# Remove single token entry
148
cache.remove(token_entry)
149
```
150
151
### Cache Serialization
152
153
Serializes the entire cache to a JSON string for persistent storage across application sessions.
154
155
```python { .api }
156
def serialize(self):
157
"""
158
Serialize cache to JSON string.
159
160
Returns:
161
str: JSON representation of cache contents
162
"""
163
```
164
165
**Usage Example:**
166
167
```python
168
# Save cache to file
169
cache_state = cache.serialize()
170
with open('token_cache.json', 'w') as f:
171
f.write(cache_state)
172
173
# Save cache to database or other storage
174
import base64
175
encoded_cache = base64.b64encode(cache_state.encode()).decode()
176
# Store encoded_cache in database
177
```
178
179
### Cache Deserialization
180
181
Loads cache contents from a previously serialized JSON string.
182
183
```python { .api }
184
def deserialize(self, state):
185
"""
186
Load cache contents from serialized state.
187
188
Parameters:
189
- state (str): JSON string from previous serialize() call
190
"""
191
```
192
193
**Usage Example:**
194
195
```python
196
# Load cache from file
197
with open('token_cache.json', 'r') as f:
198
cache_state = f.read()
199
200
cache = adal.TokenCache()
201
cache.deserialize(cache_state)
202
203
# Load from database
204
# encoded_cache = get_from_database()
205
# cache_state = base64.b64decode(encoded_cache).decode()
206
# cache.deserialize(cache_state)
207
```
208
209
### Cache Inspection
210
211
Returns all cache entries as key-value pairs for inspection and debugging.
212
213
```python { .api }
214
def read_items(self):
215
"""
216
Get all cache entries as key-value pairs.
217
218
Returns:
219
list: List of (TokenCacheKey, authentication_result) tuples
220
"""
221
```
222
223
**Usage Example:**
224
225
```python
226
# Inspect cache contents
227
items = cache.read_items()
228
print(f"Cache contains {len(items)} entries:")
229
230
for cache_key, token_data in items:
231
print(f"User: {cache_key.user_id}")
232
print(f"Resource: {cache_key.resource}")
233
print(f"Client: {cache_key.client_id}")
234
print(f"Expires: {token_data.get('expiresOn')}")
235
print("---")
236
```
237
238
## Properties
239
240
### State Change Tracking
241
242
```python { .api }
243
has_state_changed: bool # Read-only property indicating if cache has been modified
244
```
245
246
**Usage Example:**
247
248
```python
249
# Check if cache needs to be saved
250
if cache.has_state_changed:
251
cache_state = cache.serialize()
252
save_cache_to_storage(cache_state)
253
print("Cache saved")
254
else:
255
print("No changes to save")
256
```
257
258
## Complete Token Caching Example
259
260
```python
261
import adal
262
import os
263
import json
264
265
class PersistentTokenCache:
266
def __init__(self, cache_file='adal_cache.json'):
267
self.cache_file = cache_file
268
self.cache = self._load_cache()
269
270
def _load_cache(self):
271
"""Load cache from file if it exists"""
272
if os.path.exists(self.cache_file):
273
try:
274
with open(self.cache_file, 'r') as f:
275
cache_state = f.read()
276
return adal.TokenCache(state=cache_state)
277
except Exception as e:
278
print(f"Failed to load cache: {e}")
279
280
return adal.TokenCache()
281
282
def save_cache(self):
283
"""Save cache to file if it has changed"""
284
if self.cache.has_state_changed:
285
try:
286
cache_state = self.cache.serialize()
287
with open(self.cache_file, 'w') as f:
288
f.write(cache_state)
289
print(f"Cache saved to {self.cache_file}")
290
except Exception as e:
291
print(f"Failed to save cache: {e}")
292
293
def get_context(self, authority):
294
"""Get authentication context with persistent cache"""
295
return adal.AuthenticationContext(authority, cache=self.cache)
296
297
def clear_cache(self):
298
"""Clear all cached tokens"""
299
all_tokens = self.cache.find({})
300
if all_tokens:
301
self.cache.remove(all_tokens)
302
self.save_cache()
303
print("Cache cleared")
304
305
# Usage example
306
def main():
307
# Create persistent cache manager
308
cache_manager = PersistentTokenCache('my_app_cache.json')
309
310
# Get authentication context with cache
311
authority = 'https://login.microsoftonline.com/tenant-id'
312
context = cache_manager.get_context(authority)
313
314
try:
315
# Try to get token (will use cache if available)
316
token = context.acquire_token_with_client_credentials(
317
resource='https://management.azure.com/',
318
client_id='your-client-id',
319
client_secret='your-client-secret'
320
)
321
322
print("Authentication successful!")
323
print(f"Token expires: {token.get('expiresOn')}")
324
325
# Save cache for next time
326
cache_manager.save_cache()
327
328
except adal.AdalError as e:
329
print(f"Authentication failed: {e}")
330
331
# Inspect cache contents
332
cached_tokens = cache_manager.cache.find({})
333
print(f"Cache contains {len(cached_tokens)} tokens")
334
335
if __name__ == '__main__':
336
main()
337
```
338
339
## Thread Safety
340
341
TokenCache is thread-safe and can be used safely across multiple threads. All operations use internal locking to prevent race conditions.
342
343
```python
344
import threading
345
import adal
346
347
# Shared cache across threads
348
shared_cache = adal.TokenCache()
349
350
def worker_thread(thread_id):
351
context = adal.AuthenticationContext(
352
'https://login.microsoftonline.com/tenant-id',
353
cache=shared_cache
354
)
355
356
# Safe to call from multiple threads
357
token = context.acquire_token_with_client_credentials(
358
resource='https://management.azure.com/',
359
client_id='your-client-id',
360
client_secret='your-client-secret'
361
)
362
print(f"Thread {thread_id} got token")
363
364
# Create multiple threads using same cache
365
threads = []
366
for i in range(5):
367
t = threading.Thread(target=worker_thread, args=(i,))
368
threads.append(t)
369
t.start()
370
371
for t in threads:
372
t.join()
373
```