Skip to content

Commit 8dc84ac

Browse files
feat: add optional scope parameter to clearApiCredentials (#1412)
1 parent 35fb0b4 commit 8dc84ac

File tree

13 files changed

+116
-33
lines changed

13 files changed

+116
-33
lines changed

EXAMPLES.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ function MyComponent() {
492492
const clearFirstApiCache = async () => {
493493
// Clear cached credentials for a specific API
494494
await clearApiCredentials('https://first-api.example.com');
495+
496+
// Or clear with specific scope
497+
await clearApiCredentials('https://first-api.example.com', 'read:data');
495498
};
496499

497500
return (
@@ -531,6 +534,12 @@ console.log('Scope:', apiCredentials.scope);
531534
await auth0.credentialsManager.clearApiCredentials(
532535
'https://first-api.example.com'
533536
);
537+
538+
// Clear with specific scope
539+
await auth0.credentialsManager.clearApiCredentials(
540+
'https://first-api.example.com',
541+
'read:data write:data'
542+
);
534543
```
535544

536545
### Web Platform Configuration

android/src/main/java/com/auth0/react/A0Auth0Module.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,8 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
328328
}
329329

330330
@ReactMethod
331-
override fun clearApiCredentials(audience: String, promise: Promise) {
332-
secureCredentialsManager.clearApiCredentials(audience)
331+
override fun clearApiCredentials(audience: String, scope: String?, promise: Promise) {
332+
secureCredentialsManager.clearApiCredentials(audience, scope)
333333
promise.resolve(true)
334334
}
335335

android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ abstract class A0Auth0Spec(context: ReactApplicationContext) : ReactContextBaseJ
6363

6464
@ReactMethod
6565
@DoNotStrip
66-
abstract fun clearApiCredentials(audience: String, promise: Promise)
66+
abstract fun clearApiCredentials(audience: String, scope: String?, promise: Promise)
6767

6868
@ReactMethod
6969
@DoNotStrip

ios/A0Auth0.mm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ - (dispatch_queue_t)methodQueue
8787
}
8888

8989
RCT_EXPORT_METHOD(clearApiCredentials: (NSString *)audience
90+
scope:(NSString * _Nullable)scope
9091
resolve:(RCTPromiseResolveBlock)resolve
9192
reject:(RCTPromiseRejectBlock)reject) {
92-
[self.nativeBridge clearApiCredentialsWithAudience:audience resolve:resolve reject:reject];
93+
[self.nativeBridge clearApiCredentialsWithAudience:audience scope:scope resolve:resolve reject:reject];
9394
}
9495

9596

ios/NativeBridge.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,10 @@ public class NativeBridge: NSObject {
372372
}
373373

374374

375-
@objc public func clearApiCredentials(audience: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
376-
// The clear(forAudience:) method returns a boolean indicating success.
375+
@objc public func clearApiCredentials(audience: String, scope: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
376+
// The clear(forAudience:scope:) method returns a boolean indicating success.
377377
// We can resolve the promise with this boolean value.
378-
resolve(credentialsManager.clear(forAudience: audience))
378+
resolve(credentialsManager.clear(forAudience: audience, scope: scope))
379379
}
380380

381381
@objc public func getClientId() -> String {

src/core/interfaces/ICredentialsManager.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,24 @@ export interface ICredentialsManager {
136136

137137
/**
138138
* Removes cached credentials for a specific audience.
139+
* Optionally filter by scope to clear only specific scope-based credentials.
139140
*
140141
* This clears the stored API credentials for the given audience, forcing the next
141142
* `getApiCredentials` call for this audience to perform a fresh token exchange.
142143
*
143144
* @param audience The identifier of the API for which to clear credentials.
145+
* @param scope Optional scope to clear. If credentials were fetched with a scope, it is recommended to pass the same scope when clearing them.
144146
* @returns A promise that resolves when the credentials are cleared.
145147
* @throws {CredentialsManagerError} If the operation fails.
146148
*
147149
* @example
148150
* ```typescript
151+
* // Clear all credentials for an audience
149152
* await credentialsManager.clearApiCredentials('https://api.example.com');
153+
*
154+
* // Clear credentials for specific scope (recommended)
155+
* await credentialsManager.clearApiCredentials('https://api.example.com', 'read:data');
150156
* ```
151157
*/
152-
clearApiCredentials(audience: string): Promise<void>;
158+
clearApiCredentials(audience: string, scope?: string): Promise<void>;
153159
}

src/platforms/native/adapters/NativeCredentialsManager.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,12 @@ export class NativeCredentialsManager implements ICredentialsManager {
4040
);
4141
}
4242

43-
hasValidCredentials(minTtl?: number): Promise<boolean> {
44-
return this.handleError(this.bridge.hasValidCredentials(minTtl));
43+
async clearCredentials(): Promise<void> {
44+
return this.handleError(this.bridge.clearCredentials());
4545
}
4646

47-
async clearCredentials(): Promise<void> {
48-
await this.handleError(this.bridge.clearCredentials());
49-
// Also clear the DPoP key when clearing credentials
50-
// Ignore errors from DPoP key clearing - this matches iOS behavior
51-
// where we log the error but don't fail the operation
52-
try {
53-
await this.bridge.clearDPoPKey();
54-
} catch {
55-
// Silently ignore DPoP key clearing errors
56-
// The main credentials are already cleared at this point
57-
}
47+
async hasValidCredentials(minTtl?: number): Promise<boolean> {
48+
return this.handleError(this.bridge.hasValidCredentials(minTtl));
5849
}
5950

6051
async getApiCredentials(
@@ -70,8 +61,8 @@ export class NativeCredentialsManager implements ICredentialsManager {
7061
return new ApiCredentials(nativeCredentials as IApiCredentials);
7162
}
7263

73-
clearApiCredentials(audience: string): Promise<void> {
74-
return this.handleError(this.bridge.clearApiCredentials(audience));
64+
async clearApiCredentials(audience: string, scope?: string): Promise<void> {
65+
return this.handleError(this.bridge.clearApiCredentials(audience, scope));
7566
}
7667

7768
getSSOCredentials(

src/platforms/native/adapters/__tests__/NativeCredentialsManager.spec.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,54 @@ describe('NativeCredentialsManager', () => {
357357
).rejects.toThrow(CredentialsManagerError);
358358
});
359359

360-
it('should clear credentials on success', async () => {
360+
it('should clear credentials for audience without scope', async () => {
361361
mockBridge.clearApiCredentials.mockResolvedValue(undefined);
362362

363363
await expect(
364364
manager.clearApiCredentials('https://api.example.com')
365365
).resolves.toBeUndefined();
366366

367367
expect(mockBridge.clearApiCredentials).toHaveBeenCalledWith(
368-
'https://api.example.com'
368+
'https://api.example.com',
369+
undefined
370+
);
371+
});
372+
373+
it('should clear credentials for audience with scope', async () => {
374+
mockBridge.clearApiCredentials.mockResolvedValue(undefined);
375+
376+
await expect(
377+
manager.clearApiCredentials(
378+
'https://api.example.com',
379+
'read:data write:data'
380+
)
381+
).resolves.toBeUndefined();
382+
383+
expect(mockBridge.clearApiCredentials).toHaveBeenCalledWith(
384+
'https://api.example.com',
385+
'read:data write:data'
386+
);
387+
});
388+
389+
it('should handle multiple different audiences', async () => {
390+
mockBridge.clearApiCredentials.mockResolvedValue(undefined);
391+
392+
await manager.clearApiCredentials('https://api1.example.com');
393+
await manager.clearApiCredentials(
394+
'https://api2.example.com',
395+
'admin:write'
396+
);
397+
398+
expect(mockBridge.clearApiCredentials).toHaveBeenCalledTimes(2);
399+
expect(mockBridge.clearApiCredentials).toHaveBeenNthCalledWith(
400+
1,
401+
'https://api1.example.com',
402+
undefined
403+
);
404+
expect(mockBridge.clearApiCredentials).toHaveBeenNthCalledWith(
405+
2,
406+
'https://api2.example.com',
407+
'admin:write'
369408
);
370409
});
371410
});

src/platforms/native/bridge/INativeBridge.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,15 @@ export interface INativeBridge {
123123
parameters?: object
124124
): Promise<ApiCredentials>;
125125

126-
clearApiCredentials(audience: string): Promise<void>;
126+
/**
127+
* Clears API credentials for a specific audience from secure storage.
128+
* Optionally filter by scope to clear only specific scope-based credentials.
129+
*
130+
* @param audience The audience of the API.
131+
* @param scope Optional scope to clear. If credentials were fetched with a scope, it is recommended to pass the same scope when clearing them.
132+
*/
133+
clearApiCredentials(audience: string, scope?: string): Promise<void>;
134+
127135
/**
128136
* Clears credentials from secure storage.
129137
*/

src/platforms/native/bridge/NativeBridgeManager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,11 @@ export class NativeBridgeManager implements INativeBridge {
165165
);
166166
}
167167

168-
clearApiCredentials(audience: string): Promise<void> {
168+
clearApiCredentials(audience: string, scope?: string): Promise<void> {
169169
return this.a0_call(
170170
Auth0NativeModule.clearApiCredentials.bind(Auth0NativeModule),
171-
audience
171+
audience,
172+
scope
172173
);
173174
}
174175

0 commit comments

Comments
 (0)