Skip to content

Commit 46c5dab

Browse files
feat: complete error code constants implementation and add example usage (#1413)
1 parent 8dc84ac commit 46c5dab

File tree

10 files changed

+559
-21
lines changed

10 files changed

+559
-21
lines changed

README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,13 +640,51 @@ try {
640640

641641
**Platform agnostic errors:**
642642

643-
You can access the platform agnostic generic error codes as below :
643+
You can access the platform agnostic generic error codes as below:
644644

645645
```js
646646
try {
647647
const credentials = await auth0.credentialsManager.getCredentials();
648648
} catch (error) {
649-
console.log(e.type);
649+
console.log(error.type);
650+
}
651+
```
652+
653+
**Using Error Code Constants (Recommended)**
654+
655+
For better type safety and autocompletion, you can use the exported error code constants:
656+
657+
```js
658+
import {
659+
CredentialsManagerError,
660+
CredentialsManagerErrorCodes,
661+
} from 'react-native-auth0';
662+
663+
try {
664+
const credentials = await auth0.credentialsManager.getCredentials();
665+
} catch (error) {
666+
if (error instanceof CredentialsManagerError) {
667+
switch (error.type) {
668+
case CredentialsManagerErrorCodes.NO_CREDENTIALS:
669+
console.log('No credentials stored. User needs to log in.');
670+
break;
671+
case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
672+
console.log(
673+
'No refresh token available. Request offline_access scope during login.'
674+
);
675+
break;
676+
case CredentialsManagerErrorCodes.RENEW_FAILED:
677+
console.log(
678+
'Failed to refresh credentials. Re-authentication may be required.'
679+
);
680+
break;
681+
case CredentialsManagerErrorCodes.BIOMETRICS_FAILED:
682+
console.log('Biometric authentication failed.');
683+
break;
684+
default:
685+
console.error('Credentials error:', error.message);
686+
}
687+
}
650688
}
651689
```
652690

@@ -707,6 +745,34 @@ try {
707745
}
708746
```
709747

748+
**Using Error Code Constants (Recommended)**
749+
750+
For better type safety and autocompletion, you can use the exported error code constants:
751+
752+
```javascript
753+
import { WebAuthError, WebAuthErrorCodes } from 'react-native-auth0';
754+
755+
try {
756+
await auth0.webAuth.authorize();
757+
} catch (e) {
758+
if (e instanceof WebAuthError) {
759+
switch (e.type) {
760+
case WebAuthErrorCodes.USER_CANCELLED:
761+
console.log('User cancelled the login.');
762+
break;
763+
case WebAuthErrorCodes.NETWORK_ERROR:
764+
console.log('Network error occurred. Please check your connection.');
765+
break;
766+
case WebAuthErrorCodes.BROWSER_NOT_AVAILABLE:
767+
console.log('No browser available on this device.');
768+
break;
769+
default:
770+
console.error('Authentication error:', e.message);
771+
}
772+
}
773+
}
774+
```
775+
710776
| Platform-Agnostic | Description | Android Native Error | iOS Native Error | Web Error Code |
711777
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------------ |
712778
| `USER_CANCELLED` | The user actively cancelled the web authentication flow. | `a0.session.user_cancelled` | `USER_CANCELLED` | `cancelled` |

example/src/screens/hooks/CredentialsScreen.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import {
88
Linking,
99
Alert,
1010
} from 'react-native';
11-
import { useAuth0, Credentials, ApiCredentials } from 'react-native-auth0';
11+
import {
12+
useAuth0,
13+
Credentials,
14+
ApiCredentials,
15+
CredentialsManagerError,
16+
CredentialsManagerErrorCodes,
17+
} from 'react-native-auth0';
1218
import Button from '../../components/Button';
1319
import Header from '../../components/Header';
1420
import Result from '../../components/Result';
@@ -41,6 +47,28 @@ const CredentialsScreen = () => {
4147
setResult(res ?? { success: `${title} completed` });
4248
} catch (e) {
4349
setError(e as Error);
50+
// Demonstrate usage of CredentialsManagerErrorCodes for type-safe error handling
51+
if (e instanceof CredentialsManagerError) {
52+
const credError: CredentialsManagerError = e;
53+
switch (credError.type) {
54+
case CredentialsManagerErrorCodes.NO_CREDENTIALS:
55+
Alert.alert(
56+
'No Credentials',
57+
'No credentials are stored. Please log in first.'
58+
);
59+
break;
60+
case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
61+
Alert.alert(
62+
'No Refresh Token',
63+
'Refresh token is not available. Make sure to request the "offline_access" scope during login.'
64+
);
65+
break;
66+
default:
67+
console.log(
68+
`Credentials error: ${credError.type} - ${credError.message}`
69+
);
70+
}
71+
}
4472
}
4573
};
4674

example/src/screens/hooks/Home.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
StyleSheet,
88
Alert,
99
} from 'react-native';
10-
import { useAuth0 } from 'react-native-auth0';
10+
import { useAuth0, WebAuthError, WebAuthErrorCodes } from 'react-native-auth0';
1111
import Button from '../../components/Button';
1212
import Header from '../../components/Header';
1313
import LabeledInput from '../../components/LabeledInput';
@@ -37,6 +37,28 @@ const HomeScreen = () => {
3737
});
3838
} catch (e) {
3939
console.log('Login error: ', e);
40+
// Demonstrate usage of WebAuthErrorCodes for type-safe error handling
41+
if (e instanceof WebAuthError) {
42+
const webAuthError: WebAuthError = e;
43+
switch (webAuthError.type) {
44+
case WebAuthErrorCodes.USER_CANCELLED:
45+
Alert.alert(
46+
'Login Cancelled',
47+
'You cancelled the login process. Please try again when ready.'
48+
);
49+
break;
50+
case WebAuthErrorCodes.TIMEOUT_ERROR:
51+
Alert.alert(
52+
'Login Timeout',
53+
'The login process timed out. Please try again.'
54+
);
55+
break;
56+
default:
57+
Alert.alert('Authentication Error', webAuthError.message);
58+
}
59+
} else {
60+
Alert.alert('Error', 'An unexpected error occurred during login.');
61+
}
4062
}
4163
};
4264

src/core/models/CredentialsManagerError.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,67 @@
11
import { AuthError } from './AuthError';
22

33
/**
4-
* Public constants exposing all possible CredentialsManager error codes.
4+
* Platform-agnostic error code constants for Credentials Manager operations.
5+
*
6+
* Use these constants for type-safe error handling when working with credentials operations
7+
* like getCredentials, saveCredentials, clearCredentials, and getApiCredentials.
8+
* Each constant corresponds to a specific error type in the {@link CredentialsManagerError.type} property.
9+
*
10+
* @example
11+
* ```typescript
12+
* import { CredentialsManagerError, CredentialsManagerErrorCodes } from 'react-native-auth0';
13+
*
14+
* try {
15+
* const credentials = await auth0.credentialsManager.getCredentials();
16+
* } catch (e) {
17+
* if (e instanceof CredentialsManagerError) {
18+
* switch (e.type) {
19+
* case CredentialsManagerErrorCodes.NO_CREDENTIALS:
20+
* // User needs to log in
21+
* break;
22+
* case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
23+
* // Request offline_access scope during login
24+
* break;
25+
* case CredentialsManagerErrorCodes.RENEW_FAILED:
26+
* // Token refresh failed - may need re-authentication
27+
* break;
28+
* }
29+
* }
30+
* }
31+
* ```
32+
*
33+
* @see {@link CredentialsManagerError}
534
*/
635
export const CredentialsManagerErrorCodes = {
36+
/** Stored credentials are invalid or corrupted */
737
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
38+
/** No credentials are stored - user needs to log in */
839
NO_CREDENTIALS: 'NO_CREDENTIALS',
40+
/** Refresh token is not available - ensure offline_access scope was requested */
941
NO_REFRESH_TOKEN: 'NO_REFRESH_TOKEN',
42+
/** Failed to refresh credentials using refresh token */
1043
RENEW_FAILED: 'RENEW_FAILED',
44+
/** Failed to store credentials securely */
1145
STORE_FAILED: 'STORE_FAILED',
46+
/** Failed to revoke refresh token */
1247
REVOKE_FAILED: 'REVOKE_FAILED',
48+
/** Requested minimum TTL exceeds token lifetime */
1349
LARGE_MIN_TTL: 'LARGE_MIN_TTL',
50+
/** Generic credentials manager error */
1451
CREDENTIAL_MANAGER_ERROR: 'CREDENTIAL_MANAGER_ERROR',
52+
/** Biometric authentication failed */
1553
BIOMETRICS_FAILED: 'BIOMETRICS_FAILED',
54+
/** Network connectivity issue */
1655
NO_NETWORK: 'NO_NETWORK',
56+
/** Generic API error */
1757
API_ERROR: 'API_ERROR',
58+
/** Failed to exchange refresh token for API-specific credentials (MRRT) */
1859
API_EXCHANGE_FAILED: 'API_EXCHANGE_FAILED',
60+
/** Device is incompatible with secure storage requirements */
1961
INCOMPATIBLE_DEVICE: 'INCOMPATIBLE_DEVICE',
62+
/** Cryptographic operation failed */
2063
CRYPTO_EXCEPTION: 'CRYPTO_EXCEPTION',
64+
/** Unknown or uncategorized error */
2165
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
2266
} as const;
2367

src/core/models/DPoPError.ts

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,92 @@
11
import { AuthError } from './AuthError';
22

3-
const ERROR_CODE_MAP: Record<string, string> = {
4-
// --- DPoP-specific error codes ---
3+
/**
4+
* Platform-agnostic error code constants for DPoP (Demonstrating Proof-of-Possession) operations.
5+
*
6+
* Use these constants for type-safe error handling when working with DPoP-bound tokens.
7+
* DPoP enhances OAuth 2.0 security by binding tokens to cryptographic keys.
8+
* Each constant corresponds to a specific error type in the {@link DPoPError.type} property.
9+
*
10+
* @example
11+
* ```typescript
12+
* import { DPoPError, DPoPErrorCodes } from 'react-native-auth0';
13+
*
14+
* try {
15+
* const headers = await auth0.getDPoPHeaders({
16+
* url: 'https://api.example.com/data',
17+
* method: 'GET',
18+
* accessToken: credentials.accessToken,
19+
* tokenType: credentials.tokenType
20+
* });
21+
* } catch (e) {
22+
* if (e instanceof DPoPError) {
23+
* switch (e.type) {
24+
* case DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED:
25+
* // Failed to generate DPoP key pair
26+
* break;
27+
* case DPoPErrorCodes.DPOP_PROOF_FAILED:
28+
* // Failed to create DPoP proof
29+
* break;
30+
* }
31+
* }
32+
* }
33+
* ```
34+
*
35+
* @see {@link DPoPError}
36+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9449|RFC 9449 - OAuth 2.0 DPoP}
37+
*/
38+
export const DPoPErrorCodes = {
39+
/** Failed to generate DPoP proof JWT */
540
DPOP_GENERATION_FAILED: 'DPOP_GENERATION_FAILED',
41+
/** DPoP proof validation or creation failed */
642
DPOP_PROOF_FAILED: 'DPOP_PROOF_FAILED',
43+
/** Failed to generate DPoP key pair */
744
DPOP_KEY_GENERATION_FAILED: 'DPOP_KEY_GENERATION_FAILED',
45+
/** Failed to store DPoP key securely (keychain/keystore) */
846
DPOP_KEY_STORAGE_FAILED: 'DPOP_KEY_STORAGE_FAILED',
47+
/** Failed to retrieve stored DPoP key */
948
DPOP_KEY_RETRIEVAL_FAILED: 'DPOP_KEY_RETRIEVAL_FAILED',
49+
/** DPoP nonce mismatch - server rejected the proof */
1050
DPOP_NONCE_MISMATCH: 'DPOP_NONCE_MISMATCH',
51+
/** Invalid token type for DPoP operation */
1152
DPOP_INVALID_TOKEN_TYPE: 'DPOP_INVALID_TOKEN_TYPE',
53+
/** Required DPoP parameter is missing */
1254
DPOP_MISSING_PARAMETER: 'DPOP_MISSING_PARAMETER',
55+
/** Failed to clear/delete DPoP key */
1356
DPOP_CLEAR_KEY_FAILED: 'DPOP_CLEAR_KEY_FAILED',
57+
/** Unknown or uncategorized DPoP error */
58+
UNKNOWN_DPOP_ERROR: 'UNKNOWN_DPOP_ERROR',
59+
} as const;
60+
61+
const ERROR_CODE_MAP: Record<string, string> = {
62+
// --- DPoP-specific error codes ---
63+
DPOP_GENERATION_FAILED: DPoPErrorCodes.DPOP_GENERATION_FAILED,
64+
DPOP_PROOF_FAILED: DPoPErrorCodes.DPOP_PROOF_FAILED,
65+
DPOP_KEY_GENERATION_FAILED: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,
66+
DPOP_KEY_STORAGE_FAILED: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,
67+
DPOP_KEY_RETRIEVAL_FAILED: DPoPErrorCodes.DPOP_KEY_RETRIEVAL_FAILED,
68+
DPOP_NONCE_MISMATCH: DPoPErrorCodes.DPOP_NONCE_MISMATCH,
69+
DPOP_INVALID_TOKEN_TYPE: DPoPErrorCodes.DPOP_INVALID_TOKEN_TYPE,
70+
DPOP_MISSING_PARAMETER: DPoPErrorCodes.DPOP_MISSING_PARAMETER,
71+
DPOP_CLEAR_KEY_FAILED: DPoPErrorCodes.DPOP_CLEAR_KEY_FAILED,
1472

1573
// --- Native platform mappings ---
1674
// iOS
17-
DPOP_KEY_NOT_FOUND: 'DPOP_KEY_RETRIEVAL_FAILED',
18-
DPOP_KEYCHAIN_ERROR: 'DPOP_KEY_STORAGE_FAILED',
75+
DPOP_KEY_NOT_FOUND: DPoPErrorCodes.DPOP_KEY_RETRIEVAL_FAILED,
76+
DPOP_KEYCHAIN_ERROR: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,
1977

2078
// Android
21-
DPOP_KEYSTORE_ERROR: 'DPOP_KEY_STORAGE_FAILED',
22-
DPOP_CRYPTO_ERROR: 'DPOP_KEY_GENERATION_FAILED',
79+
DPOP_KEYSTORE_ERROR: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,
80+
DPOP_CRYPTO_ERROR: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,
2381

2482
// Web
25-
dpop_generation_failed: 'DPOP_GENERATION_FAILED',
26-
dpop_proof_failed: 'DPOP_PROOF_FAILED',
27-
dpop_key_error: 'DPOP_KEY_GENERATION_FAILED',
83+
dpop_generation_failed: DPoPErrorCodes.DPOP_GENERATION_FAILED,
84+
dpop_proof_failed: DPoPErrorCodes.DPOP_PROOF_FAILED,
85+
dpop_key_error: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,
2886

2987
// --- Generic fallback ---
30-
UNKNOWN: 'UNKNOWN_DPOP_ERROR',
31-
OTHER: 'UNKNOWN_DPOP_ERROR',
88+
UNKNOWN: DPoPErrorCodes.UNKNOWN_DPOP_ERROR,
89+
OTHER: DPoPErrorCodes.UNKNOWN_DPOP_ERROR,
3290
};
3391

3492
/**
@@ -86,6 +144,7 @@ export class DPoPError extends AuthError {
86144
});
87145

88146
// Map the original error code to a normalized type
89-
this.type = ERROR_CODE_MAP[originalError.code] || 'UNKNOWN_DPOP_ERROR';
147+
this.type =
148+
ERROR_CODE_MAP[originalError.code] || DPoPErrorCodes.UNKNOWN_DPOP_ERROR;
90149
}
91150
}

0 commit comments

Comments
 (0)