Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,9 @@ function MyComponent() {
const clearFirstApiCache = async () => {
// Clear cached credentials for a specific API
await clearApiCredentials('https://first-api.example.com');

// Or clear with specific scope
await clearApiCredentials('https://first-api.example.com', 'read:data');
};

return (
Expand Down Expand Up @@ -531,6 +534,12 @@ console.log('Scope:', apiCredentials.scope);
await auth0.credentialsManager.clearApiCredentials(
'https://first-api.example.com'
);

// Clear with specific scope
await auth0.credentialsManager.clearApiCredentials(
'https://first-api.example.com',
'read:data write:data'
);
```

### Web Platform Configuration
Expand Down
4 changes: 2 additions & 2 deletions android/src/main/java/com/auth0/react/A0Auth0Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
}

@ReactMethod
override fun clearApiCredentials(audience: String, promise: Promise) {
secureCredentialsManager.clearApiCredentials(audience)
override fun clearApiCredentials(audience: String, scope: String?, promise: Promise) {
secureCredentialsManager.clearApiCredentials(audience, scope)
promise.resolve(true)
}

Expand Down
2 changes: 1 addition & 1 deletion android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ abstract class A0Auth0Spec(context: ReactApplicationContext) : ReactContextBaseJ

@ReactMethod
@DoNotStrip
abstract fun clearApiCredentials(audience: String, promise: Promise)
abstract fun clearApiCredentials(audience: String, scope: String?, promise: Promise)

@ReactMethod
@DoNotStrip
Expand Down
3 changes: 2 additions & 1 deletion ios/A0Auth0.mm
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ - (dispatch_queue_t)methodQueue
}

RCT_EXPORT_METHOD(clearApiCredentials: (NSString *)audience
scope:(NSString * _Nullable)scope
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
[self.nativeBridge clearApiCredentialsWithAudience:audience resolve:resolve reject:reject];
[self.nativeBridge clearApiCredentialsWithAudience:audience scope:scope resolve:resolve reject:reject];
}


Expand Down
6 changes: 3 additions & 3 deletions ios/NativeBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ public class NativeBridge: NSObject {
}


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

@objc public func getClientId() -> String {
Expand Down
8 changes: 7 additions & 1 deletion src/core/interfaces/ICredentialsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,24 @@ export interface ICredentialsManager {

/**
* Removes cached credentials for a specific audience.
* Optionally filter by scope to clear only specific scope-based credentials.
*
* This clears the stored API credentials for the given audience, forcing the next
* `getApiCredentials` call for this audience to perform a fresh token exchange.
*
* @param audience The identifier of the API for which to clear credentials.
* @param scope Optional scope to clear. If credentials were fetched with a scope, it is recommended to pass the same scope when clearing them.
* @returns A promise that resolves when the credentials are cleared.
* @throws {CredentialsManagerError} If the operation fails.
*
* @example
* ```typescript
* // Clear all credentials for an audience
* await credentialsManager.clearApiCredentials('https://api.example.com');
*
* // Clear credentials for specific scope (recommended)
* await credentialsManager.clearApiCredentials('https://api.example.com', 'read:data');
* ```
*/
clearApiCredentials(audience: string): Promise<void>;
clearApiCredentials(audience: string, scope?: string): Promise<void>;
}
21 changes: 6 additions & 15 deletions src/platforms/native/adapters/NativeCredentialsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,12 @@ export class NativeCredentialsManager implements ICredentialsManager {
);
}

hasValidCredentials(minTtl?: number): Promise<boolean> {
return this.handleError(this.bridge.hasValidCredentials(minTtl));
async clearCredentials(): Promise<void> {
return this.handleError(this.bridge.clearCredentials());
}

async clearCredentials(): Promise<void> {
await this.handleError(this.bridge.clearCredentials());
// Also clear the DPoP key when clearing credentials
// Ignore errors from DPoP key clearing - this matches iOS behavior
// where we log the error but don't fail the operation
try {
await this.bridge.clearDPoPKey();
} catch {
// Silently ignore DPoP key clearing errors
// The main credentials are already cleared at this point
}
Comment on lines -52 to -57
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to call this. As we are calling it in the native module

async hasValidCredentials(minTtl?: number): Promise<boolean> {
return this.handleError(this.bridge.hasValidCredentials(minTtl));
}

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

clearApiCredentials(audience: string): Promise<void> {
return this.handleError(this.bridge.clearApiCredentials(audience));
async clearApiCredentials(audience: string, scope?: string): Promise<void> {
return this.handleError(this.bridge.clearApiCredentials(audience, scope));
}

getSSOCredentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,54 @@ describe('NativeCredentialsManager', () => {
).rejects.toThrow(CredentialsManagerError);
});

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

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

expect(mockBridge.clearApiCredentials).toHaveBeenCalledWith(
'https://api.example.com'
'https://api.example.com',
undefined
);
});

it('should clear credentials for audience with scope', async () => {
mockBridge.clearApiCredentials.mockResolvedValue(undefined);

await expect(
manager.clearApiCredentials(
'https://api.example.com',
'read:data write:data'
)
).resolves.toBeUndefined();

expect(mockBridge.clearApiCredentials).toHaveBeenCalledWith(
'https://api.example.com',
'read:data write:data'
);
});

it('should handle multiple different audiences', async () => {
mockBridge.clearApiCredentials.mockResolvedValue(undefined);

await manager.clearApiCredentials('https://api1.example.com');
await manager.clearApiCredentials(
'https://api2.example.com',
'admin:write'
);

expect(mockBridge.clearApiCredentials).toHaveBeenCalledTimes(2);
expect(mockBridge.clearApiCredentials).toHaveBeenNthCalledWith(
1,
'https://api1.example.com',
undefined
);
expect(mockBridge.clearApiCredentials).toHaveBeenNthCalledWith(
2,
'https://api2.example.com',
'admin:write'
);
});
});
Expand Down
10 changes: 9 additions & 1 deletion src/platforms/native/bridge/INativeBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ export interface INativeBridge {
parameters?: object
): Promise<ApiCredentials>;

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

/**
* Clears credentials from secure storage.
*/
Expand Down
5 changes: 3 additions & 2 deletions src/platforms/native/bridge/NativeBridgeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,11 @@ export class NativeBridgeManager implements INativeBridge {
);
}

clearApiCredentials(audience: string): Promise<void> {
clearApiCredentials(audience: string, scope?: string): Promise<void> {
return this.a0_call(
Auth0NativeModule.clearApiCredentials.bind(Auth0NativeModule),
audience
audience,
scope
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/platforms/web/adapters/WebCredentialsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,10 @@ export class WebCredentialsManager implements ICredentialsManager {
throw new CredentialsManagerError(authError);
}

async clearApiCredentials(audience: string): Promise<void> {
async clearApiCredentials(audience: string, scope?: string): Promise<void> {
const scopeInfo = scope ? ` and scope ${scope}` : '';
console.warn(
`'clearApiCredentials' for audience ${audience} is a no-op on the web. @auth0/auth0-spa-js handles credential storage automatically.`
`'clearApiCredentials' for audience ${audience}${scopeInfo} is a no-op on the web. @auth0/auth0-spa-js handles credential storage automatically.`
);
return Promise.resolve();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,34 @@ describe('WebCredentialsManager', () => {
});

describe('clearApiCredentials', () => {
it('should log a warning and resolve without doing anything', async () => {
it('should log a warning without scope and resolve without doing anything', async () => {
await credentialsManager.clearApiCredentials('https://api.example.com');

expect(consoleWarnSpy).toHaveBeenCalledWith(
"'clearApiCredentials' for audience https://api.example.com is a no-op on the web. @auth0/auth0-spa-js handles credential storage automatically."
);
});

it('should log a warning with scope and resolve without doing anything', async () => {
await credentialsManager.clearApiCredentials(
'https://api.example.com',
'read:data write:data'
);

expect(consoleWarnSpy).toHaveBeenCalledWith(
"'clearApiCredentials' for audience https://api.example.com and scope read:data write:data is a no-op on the web. @auth0/auth0-spa-js handles credential storage automatically."
);
});

it('should handle empty scope string', async () => {
await credentialsManager.clearApiCredentials(
'https://api.example.com',
''
);

expect(consoleWarnSpy).toHaveBeenCalledWith(
"'clearApiCredentials' for audience https://api.example.com is a no-op on the web. @auth0/auth0-spa-js handles credential storage automatically."
);
});
});
});
9 changes: 7 additions & 2 deletions src/specs/NativeA0Auth0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ export interface Spec extends TurboModule {
): Promise<ApiCredentials>;

/**
* Clear API credentials for a specific audience
* Clear API credentials for a specific audience. Optionally filter by scope.
* @param audience The audience to clear credentials for.
* @param scope The scope to clear credentials for. If not provided, clears all credentials for the audience.
*/
clearApiCredentials(audience: string): Promise<void>;
clearApiCredentials(
audience: string,
scope: string | undefined
): Promise<void>;

/**
* Start web authentication
Expand Down
Loading