Skip to content

Commit 5641058

Browse files
feat(auth): Add clearCredentials method (#1256)
1 parent 9838e62 commit 5641058

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

example/src/screens/hooks/Profile.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const ProfileScreen = () => {
1616
const {
1717
user,
1818
clearSession,
19+
clearCredentials,
1920
getCredentials,
2021
hasValidCredentials,
2122
revokeRefreshToken,
@@ -44,6 +45,28 @@ const ProfileScreen = () => {
4445
}
4546
};
4647

48+
const onClearCredentials = async () => {
49+
try {
50+
await clearCredentials();
51+
// Clear any local state
52+
setCredentials(null);
53+
setApiResult({ success: 'Credentials cleared locally' });
54+
setApiError(null);
55+
Alert.alert(
56+
'Success',
57+
'Credentials have been cleared from local storage.'
58+
);
59+
} catch (e) {
60+
console.log('Clear credentials error: ', e);
61+
setApiError(e as Error);
62+
Alert.alert(
63+
'Error',
64+
`Failed to clear credentials: ${e.message || 'Unknown error'}`,
65+
[{ text: 'OK' }]
66+
);
67+
}
68+
};
69+
4770
const onGetCredentials = async () => {
4871
try {
4972
const result = await getCredentials();
@@ -100,6 +123,12 @@ const ProfileScreen = () => {
100123
disabled={!credentials?.refreshToken}
101124
/>
102125
<View style={styles.spacer} />
126+
<Button
127+
onPress={onClearCredentials}
128+
title="Clear Credentials"
129+
style={styles.clearCredentialsButton}
130+
/>
131+
<View style={styles.spacer} />
103132
<Button
104133
onPress={onLogout}
105134
title="Log Out"
@@ -122,6 +151,9 @@ const styles = StyleSheet.create({
122151
spacer: {
123152
height: 16,
124153
},
154+
clearCredentialsButton: {
155+
backgroundColor: '#FF9800',
156+
},
125157
logoutButton: {
126158
backgroundColor: '#424242',
127159
},

src/hooks/Auth0Context.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ export interface Auth0ContextInterface extends AuthState {
5757
*/
5858
getCredentials(scope?: string, minTtl?: number): Promise<Credentials>;
5959

60+
/**
61+
* Clears the user's credentials without clearing their web session and logs them out.
62+
*
63+
* @remarks
64+
* **Platform specific:** This method is only available in the context of a Android/iOS application.
65+
* @returns A promise that resolves when the credentials have been cleared.
66+
*/
67+
clearCredentials: () => Promise<void>;
68+
6069
/**
6170
* Checks if a valid, non-expired set of credentials exists in storage.
6271
* This is a quick, local check and does not perform a network request.
@@ -200,6 +209,7 @@ const initialContext: Auth0ContextInterface = {
200209
authorize: stub,
201210
clearSession: stub,
202211
getCredentials: stub,
212+
clearCredentials: stub,
203213
hasValidCredentials: stub,
204214
loginWithPasswordRealm: stub,
205215
cancelWebAuth: stub,

src/hooks/Auth0Provider.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ export const Auth0Provider = ({
170170
[client]
171171
);
172172

173+
const clearCredentials = useCallback(async (): Promise<void> => {
174+
try {
175+
await client.credentialsManager.clearCredentials();
176+
dispatch({ type: 'LOGOUT_COMPLETE' });
177+
} catch (e) {
178+
const error = e as AuthError;
179+
dispatch({ type: 'ERROR', error });
180+
throw error;
181+
}
182+
}, [client]);
183+
173184
const cancelWebAuth = useCallback(
174185
() => voidFlow(client.webAuth.cancelWebAuth()),
175186
[client, voidFlow]
@@ -281,6 +292,7 @@ export const Auth0Provider = ({
281292
clearSession,
282293
getCredentials,
283294
hasValidCredentials,
295+
clearCredentials,
284296
cancelWebAuth,
285297
loginWithPasswordRealm,
286298
createUser,
@@ -303,6 +315,7 @@ export const Auth0Provider = ({
303315
clearSession,
304316
getCredentials,
305317
hasValidCredentials,
318+
clearCredentials,
306319
cancelWebAuth,
307320
loginWithPasswordRealm,
308321
createUser,

src/hooks/__tests__/Auth0Provider.spec.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ const TestConsumer = () => {
130130
isLoading,
131131
authorize,
132132
clearSession,
133+
clearCredentials,
133134
createUser,
134135
resetPassword,
135136
} = useAuth0();
@@ -159,6 +160,11 @@ const TestConsumer = () => {
159160
onPress={() => clearSession()}
160161
testID="logout-button"
161162
/>
163+
<Button
164+
title="Clear Credentials"
165+
onPress={() => clearCredentials()}
166+
testID="clear-credentials-button"
167+
/>
162168
<Button
163169
title="Create User"
164170
onPress={() =>
@@ -326,6 +332,46 @@ describe('Auth0Provider', () => {
326332
expect(mockClientInstance.webAuth.clearSession).toHaveBeenCalled();
327333
});
328334

335+
it('should update the state correctly after a clearCredentials call', async () => {
336+
// Start with a logged-in state
337+
mockClientInstance.credentialsManager.getCredentials.mockResolvedValueOnce({
338+
idToken: 'a.b.c',
339+
accessToken: 'access-token-123',
340+
tokenType: 'Bearer',
341+
expiresAt: Date.now() / 1000 + 3600,
342+
} as any);
343+
344+
await act(async () => {
345+
render(
346+
<Auth0Provider domain="test.com" clientId="123">
347+
<TestConsumer />
348+
</Auth0Provider>
349+
);
350+
});
351+
352+
await waitFor(() =>
353+
expect(screen.getByTestId('user-status')).toHaveTextContent(
354+
'Logged in as: Test User'
355+
)
356+
);
357+
358+
const clearCredentialsButton = screen.getByTestId(
359+
'clear-credentials-button'
360+
);
361+
await act(async () => {
362+
fireEvent.click(clearCredentialsButton);
363+
});
364+
365+
await waitFor(() =>
366+
expect(screen.getByTestId('user-status')).toHaveTextContent(
367+
'Not logged in'
368+
)
369+
);
370+
expect(
371+
mockClientInstance.credentialsManager.clearCredentials
372+
).toHaveBeenCalled();
373+
});
374+
329375
it('should update the error state if authorize fails', async () => {
330376
// Create a mock error object that looks like an AuthError
331377
const loginError = {

0 commit comments

Comments
 (0)