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
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,13 +640,51 @@ try {

**Platform agnostic errors:**

You can access the platform agnostic generic error codes as below :
You can access the platform agnostic generic error codes as below:

```js
try {
const credentials = await auth0.credentialsManager.getCredentials();
} catch (error) {
console.log(e.type);
console.log(error.type);
}
```

**Using Error Code Constants (Recommended)**

For better type safety and autocompletion, you can use the exported error code constants:

```js
import {
CredentialsManagerError,
CredentialsManagerErrorCodes,
} from 'react-native-auth0';

try {
const credentials = await auth0.credentialsManager.getCredentials();
} catch (error) {
if (error instanceof CredentialsManagerError) {
switch (error.type) {
case CredentialsManagerErrorCodes.NO_CREDENTIALS:
console.log('No credentials stored. User needs to log in.');
break;
case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
console.log(
'No refresh token available. Request offline_access scope during login.'
);
break;
case CredentialsManagerErrorCodes.RENEW_FAILED:
console.log(
'Failed to refresh credentials. Re-authentication may be required.'
);
break;
case CredentialsManagerErrorCodes.BIOMETRICS_FAILED:
console.log('Biometric authentication failed.');
break;
default:
console.error('Credentials error:', error.message);
}
}
}
```

Expand Down Expand Up @@ -707,6 +745,34 @@ try {
}
```

**Using Error Code Constants (Recommended)**

For better type safety and autocompletion, you can use the exported error code constants:

```javascript
import { WebAuthError, WebAuthErrorCodes } from 'react-native-auth0';

try {
await auth0.webAuth.authorize();
} catch (e) {
if (e instanceof WebAuthError) {
switch (e.type) {
case WebAuthErrorCodes.USER_CANCELLED:
console.log('User cancelled the login.');
break;
case WebAuthErrorCodes.NETWORK_ERROR:
console.log('Network error occurred. Please check your connection.');
break;
case WebAuthErrorCodes.BROWSER_NOT_AVAILABLE:
console.log('No browser available on this device.');
break;
default:
console.error('Authentication error:', e.message);
}
}
}
```

| Platform-Agnostic | Description | Android Native Error | iOS Native Error | Web Error Code |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------------ |
| `USER_CANCELLED` | The user actively cancelled the web authentication flow. | `a0.session.user_cancelled` | `USER_CANCELLED` | `cancelled` |
Expand Down
30 changes: 29 additions & 1 deletion example/src/screens/hooks/CredentialsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import {
Linking,
Alert,
} from 'react-native';
import { useAuth0, Credentials, ApiCredentials } from 'react-native-auth0';
import {
useAuth0,
Credentials,
ApiCredentials,
CredentialsManagerError,
CredentialsManagerErrorCodes,
} from 'react-native-auth0';
import Button from '../../components/Button';
import Header from '../../components/Header';
import Result from '../../components/Result';
Expand Down Expand Up @@ -41,6 +47,28 @@ const CredentialsScreen = () => {
setResult(res ?? { success: `${title} completed` });
} catch (e) {
setError(e as Error);
// Demonstrate usage of CredentialsManagerErrorCodes for type-safe error handling
if (e instanceof CredentialsManagerError) {
const credError: CredentialsManagerError = e;
switch (credError.type) {
case CredentialsManagerErrorCodes.NO_CREDENTIALS:
Alert.alert(
'No Credentials',
'No credentials are stored. Please log in first.'
);
break;
case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
Alert.alert(
'No Refresh Token',
'Refresh token is not available. Make sure to request the "offline_access" scope during login.'
);
break;
default:
console.log(
`Credentials error: ${credError.type} - ${credError.message}`
);
}
}
}
};

Expand Down
24 changes: 23 additions & 1 deletion example/src/screens/hooks/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
StyleSheet,
Alert,
} from 'react-native';
import { useAuth0 } from 'react-native-auth0';
import { useAuth0, WebAuthError, WebAuthErrorCodes } from 'react-native-auth0';
import Button from '../../components/Button';
import Header from '../../components/Header';
import LabeledInput from '../../components/LabeledInput';
Expand Down Expand Up @@ -37,6 +37,28 @@ const HomeScreen = () => {
});
} catch (e) {
console.log('Login error: ', e);
// Demonstrate usage of WebAuthErrorCodes for type-safe error handling
if (e instanceof WebAuthError) {
const webAuthError: WebAuthError = e;
switch (webAuthError.type) {
case WebAuthErrorCodes.USER_CANCELLED:
Alert.alert(
'Login Cancelled',
'You cancelled the login process. Please try again when ready.'
);
break;
case WebAuthErrorCodes.TIMEOUT_ERROR:
Alert.alert(
'Login Timeout',
'The login process timed out. Please try again.'
);
break;
default:
Alert.alert('Authentication Error', webAuthError.message);
}
} else {
Alert.alert('Error', 'An unexpected error occurred during login.');
}
}
};

Expand Down
46 changes: 45 additions & 1 deletion src/core/models/CredentialsManagerError.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,67 @@
import { AuthError } from './AuthError';

/**
* Public constants exposing all possible CredentialsManager error codes.
* Platform-agnostic error code constants for Credentials Manager operations.
*
* Use these constants for type-safe error handling when working with credentials operations
* like getCredentials, saveCredentials, clearCredentials, and getApiCredentials.
* Each constant corresponds to a specific error type in the {@link CredentialsManagerError.type} property.
*
* @example
* ```typescript
* import { CredentialsManagerError, CredentialsManagerErrorCodes } from 'react-native-auth0';
*
* try {
* const credentials = await auth0.credentialsManager.getCredentials();
* } catch (e) {
* if (e instanceof CredentialsManagerError) {
* switch (e.type) {
* case CredentialsManagerErrorCodes.NO_CREDENTIALS:
* // User needs to log in
* break;
* case CredentialsManagerErrorCodes.NO_REFRESH_TOKEN:
* // Request offline_access scope during login
* break;
* case CredentialsManagerErrorCodes.RENEW_FAILED:
* // Token refresh failed - may need re-authentication
* break;
* }
* }
* }
* ```
*
* @see {@link CredentialsManagerError}
*/
export const CredentialsManagerErrorCodes = {
/** Stored credentials are invalid or corrupted */
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
/** No credentials are stored - user needs to log in */
NO_CREDENTIALS: 'NO_CREDENTIALS',
/** Refresh token is not available - ensure offline_access scope was requested */
NO_REFRESH_TOKEN: 'NO_REFRESH_TOKEN',
/** Failed to refresh credentials using refresh token */
RENEW_FAILED: 'RENEW_FAILED',
/** Failed to store credentials securely */
STORE_FAILED: 'STORE_FAILED',
/** Failed to revoke refresh token */
REVOKE_FAILED: 'REVOKE_FAILED',
/** Requested minimum TTL exceeds token lifetime */
LARGE_MIN_TTL: 'LARGE_MIN_TTL',
/** Generic credentials manager error */
CREDENTIAL_MANAGER_ERROR: 'CREDENTIAL_MANAGER_ERROR',
/** Biometric authentication failed */
BIOMETRICS_FAILED: 'BIOMETRICS_FAILED',
/** Network connectivity issue */
NO_NETWORK: 'NO_NETWORK',
/** Generic API error */
API_ERROR: 'API_ERROR',
/** Failed to exchange refresh token for API-specific credentials (MRRT) */
API_EXCHANGE_FAILED: 'API_EXCHANGE_FAILED',
/** Device is incompatible with secure storage requirements */
INCOMPATIBLE_DEVICE: 'INCOMPATIBLE_DEVICE',
/** Cryptographic operation failed */
CRYPTO_EXCEPTION: 'CRYPTO_EXCEPTION',
/** Unknown or uncategorized error */
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
} as const;

Expand Down
83 changes: 71 additions & 12 deletions src/core/models/DPoPError.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,92 @@
import { AuthError } from './AuthError';

const ERROR_CODE_MAP: Record<string, string> = {
// --- DPoP-specific error codes ---
/**
* Platform-agnostic error code constants for DPoP (Demonstrating Proof-of-Possession) operations.
*
* Use these constants for type-safe error handling when working with DPoP-bound tokens.
* DPoP enhances OAuth 2.0 security by binding tokens to cryptographic keys.
* Each constant corresponds to a specific error type in the {@link DPoPError.type} property.
*
* @example
* ```typescript
* import { DPoPError, DPoPErrorCodes } from 'react-native-auth0';
*
* try {
* const headers = await auth0.getDPoPHeaders({
* url: 'https://api.example.com/data',
* method: 'GET',
* accessToken: credentials.accessToken,
* tokenType: credentials.tokenType
* });
* } catch (e) {
* if (e instanceof DPoPError) {
* switch (e.type) {
* case DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED:
* // Failed to generate DPoP key pair
* break;
* case DPoPErrorCodes.DPOP_PROOF_FAILED:
* // Failed to create DPoP proof
* break;
* }
* }
* }
* ```
*
* @see {@link DPoPError}
* @see {@link https://datatracker.ietf.org/doc/html/rfc9449|RFC 9449 - OAuth 2.0 DPoP}
*/
export const DPoPErrorCodes = {
/** Failed to generate DPoP proof JWT */
DPOP_GENERATION_FAILED: 'DPOP_GENERATION_FAILED',
/** DPoP proof validation or creation failed */
DPOP_PROOF_FAILED: 'DPOP_PROOF_FAILED',
/** Failed to generate DPoP key pair */
DPOP_KEY_GENERATION_FAILED: 'DPOP_KEY_GENERATION_FAILED',
/** Failed to store DPoP key securely (keychain/keystore) */
DPOP_KEY_STORAGE_FAILED: 'DPOP_KEY_STORAGE_FAILED',
/** Failed to retrieve stored DPoP key */
DPOP_KEY_RETRIEVAL_FAILED: 'DPOP_KEY_RETRIEVAL_FAILED',
/** DPoP nonce mismatch - server rejected the proof */
DPOP_NONCE_MISMATCH: 'DPOP_NONCE_MISMATCH',
/** Invalid token type for DPoP operation */
DPOP_INVALID_TOKEN_TYPE: 'DPOP_INVALID_TOKEN_TYPE',
/** Required DPoP parameter is missing */
DPOP_MISSING_PARAMETER: 'DPOP_MISSING_PARAMETER',
/** Failed to clear/delete DPoP key */
DPOP_CLEAR_KEY_FAILED: 'DPOP_CLEAR_KEY_FAILED',
/** Unknown or uncategorized DPoP error */
UNKNOWN_DPOP_ERROR: 'UNKNOWN_DPOP_ERROR',
} as const;

const ERROR_CODE_MAP: Record<string, string> = {
// --- DPoP-specific error codes ---
DPOP_GENERATION_FAILED: DPoPErrorCodes.DPOP_GENERATION_FAILED,
DPOP_PROOF_FAILED: DPoPErrorCodes.DPOP_PROOF_FAILED,
DPOP_KEY_GENERATION_FAILED: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,
DPOP_KEY_STORAGE_FAILED: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,
DPOP_KEY_RETRIEVAL_FAILED: DPoPErrorCodes.DPOP_KEY_RETRIEVAL_FAILED,
DPOP_NONCE_MISMATCH: DPoPErrorCodes.DPOP_NONCE_MISMATCH,
DPOP_INVALID_TOKEN_TYPE: DPoPErrorCodes.DPOP_INVALID_TOKEN_TYPE,
DPOP_MISSING_PARAMETER: DPoPErrorCodes.DPOP_MISSING_PARAMETER,
DPOP_CLEAR_KEY_FAILED: DPoPErrorCodes.DPOP_CLEAR_KEY_FAILED,

// --- Native platform mappings ---
// iOS
DPOP_KEY_NOT_FOUND: 'DPOP_KEY_RETRIEVAL_FAILED',
DPOP_KEYCHAIN_ERROR: 'DPOP_KEY_STORAGE_FAILED',
DPOP_KEY_NOT_FOUND: DPoPErrorCodes.DPOP_KEY_RETRIEVAL_FAILED,
DPOP_KEYCHAIN_ERROR: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,

// Android
DPOP_KEYSTORE_ERROR: 'DPOP_KEY_STORAGE_FAILED',
DPOP_CRYPTO_ERROR: 'DPOP_KEY_GENERATION_FAILED',
DPOP_KEYSTORE_ERROR: DPoPErrorCodes.DPOP_KEY_STORAGE_FAILED,
DPOP_CRYPTO_ERROR: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,

// Web
dpop_generation_failed: 'DPOP_GENERATION_FAILED',
dpop_proof_failed: 'DPOP_PROOF_FAILED',
dpop_key_error: 'DPOP_KEY_GENERATION_FAILED',
dpop_generation_failed: DPoPErrorCodes.DPOP_GENERATION_FAILED,
dpop_proof_failed: DPoPErrorCodes.DPOP_PROOF_FAILED,
dpop_key_error: DPoPErrorCodes.DPOP_KEY_GENERATION_FAILED,

// --- Generic fallback ---
UNKNOWN: 'UNKNOWN_DPOP_ERROR',
OTHER: 'UNKNOWN_DPOP_ERROR',
UNKNOWN: DPoPErrorCodes.UNKNOWN_DPOP_ERROR,
OTHER: DPoPErrorCodes.UNKNOWN_DPOP_ERROR,
};

/**
Expand Down Expand Up @@ -86,6 +144,7 @@ export class DPoPError extends AuthError {
});

// Map the original error code to a normalized type
this.type = ERROR_CODE_MAP[originalError.code] || 'UNKNOWN_DPOP_ERROR';
this.type =
ERROR_CODE_MAP[originalError.code] || DPoPErrorCodes.UNKNOWN_DPOP_ERROR;
}
}
Loading
Loading