Skip to content

Commit 52347ed

Browse files
feat: surface DPoP credential state errors from native SDKs (#1529)
1 parent 938a8c7 commit 52347ed

8 files changed

Lines changed: 105 additions & 19 deletions

File tree

A0Auth0.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
1616
s.source_files = 'ios/**/*.{h,m,mm,swift}'
1717
s.requires_arc = true
1818

19-
s.dependency 'Auth0', '2.18.0'
19+
s.dependency 'Auth0', '2.19.0'
2020

2121
install_modules_dependencies(s)
2222
end

README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -723,27 +723,37 @@ try {
723723
case CredentialsManagerErrorCodes.BIOMETRICS_FAILED:
724724
console.log('Biometric authentication failed.');
725725
break;
726+
case CredentialsManagerErrorCodes.DPOP_KEY_MISSING:
727+
case CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH:
728+
case CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED:
729+
console.log(
730+
'DPoP credential state error. Clear credentials and re-authenticate.'
731+
);
732+
break;
726733
default:
727734
console.error('Credentials error:', error.message);
728735
}
729736
}
730737
}
731738
```
732739

733-
| Generic Error Code | Android Native Error | iOS Native Error | Web Error Code |
734-
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----------------------------------------------------------- |
735-
| `INVALID_CREDENTIALS` | `INVALID_CREDENTIALS` | | |
736-
| `NO_CREDENTIALS` | `NO_CREDENTIALS` | `noCredentials` | `login_required` |
737-
| `NO_REFRESH_TOKEN` | `NO_REFRESH_TOKEN` | `noRefreshToken` |
738-
| `RENEW_FAILED` | `RENEW_FAILED` | `renewFailed` | `missing_refresh_token`, `invalid_grant`,`consent_required` |
739-
| `STORE_FAILED` | `STORE_FAILED` | `storeFailed` | |
740-
| `REVOKE_FAILED` | `REVOKE_FAILED` | `revokeFailed` | |
741-
| `LARGE_MIN_TTL` | `LARGE_MIN_TTL` | `largeMinTTL` | |
742-
| `INCOMPATIBLE_DEVICE` | `INCOMPATIBLE_DEVICE` | | |
743-
| `CRYPTO_EXCEPTION` | `CRYPTO_EXCEPTION` | | |
744-
| `BIOMETRICS_FAILED` | OneOf <br>`BIOMETRIC_NO_ACTIVITY`,`BIOMETRIC_ERROR_STATUS_UNKNOWN`,`BIOMETRIC_ERROR_UNSUPPORTED`,<br>`BIOMETRIC_ERROR_HW_UNAVAILABLE`,`BIOMETRIC_ERROR_NONE_ENROLLED`,`BIOMETRIC_ERROR_NO_HARDWARE`,<br>`BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED`,`BIOMETRIC_AUTHENTICATION_CHECK_FAILED`,<br>`BIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE` | `biometricsFailed` | |
745-
| `NO_NETWORK` | `NO_NETWORK` | | |
746-
| `API_ERROR` | `API_ERROR` | | |
740+
| Generic Error Code | Android Native Error | iOS Native Error | Web Error Code |
741+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ----------------------------------------------------------- |
742+
| `INVALID_CREDENTIALS` | `INVALID_CREDENTIALS` | | |
743+
| `NO_CREDENTIALS` | `NO_CREDENTIALS` | `noCredentials` | `login_required` |
744+
| `NO_REFRESH_TOKEN` | `NO_REFRESH_TOKEN` | `noRefreshToken` |
745+
| `RENEW_FAILED` | `RENEW_FAILED` | `renewFailed` | `missing_refresh_token`, `invalid_grant`,`consent_required` |
746+
| `STORE_FAILED` | `STORE_FAILED` | `storeFailed` | |
747+
| `REVOKE_FAILED` | `REVOKE_FAILED` | `revokeFailed` | |
748+
| `LARGE_MIN_TTL` | `LARGE_MIN_TTL` | `largeMinTTL` | |
749+
| `INCOMPATIBLE_DEVICE` | `INCOMPATIBLE_DEVICE` | | |
750+
| `CRYPTO_EXCEPTION` | `CRYPTO_EXCEPTION` | | |
751+
| `BIOMETRICS_FAILED` | OneOf <br>`BIOMETRIC_NO_ACTIVITY`,`BIOMETRIC_ERROR_STATUS_UNKNOWN`,`BIOMETRIC_ERROR_UNSUPPORTED`,<br>`BIOMETRIC_ERROR_HW_UNAVAILABLE`,`BIOMETRIC_ERROR_NONE_ENROLLED`,`BIOMETRIC_ERROR_NO_HARDWARE`,<br>`BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED`,`BIOMETRIC_AUTHENTICATION_CHECK_FAILED`,<br>`BIOMETRIC_ERROR_DEVICE_CREDENTIAL_NOT_AVAILABLE` | `biometricsFailed` | |
752+
| `NO_NETWORK` | `NO_NETWORK` | | |
753+
| `API_ERROR` | `API_ERROR` | | |
754+
| `DPOP_KEY_MISSING` | `DPOP_KEY_MISSING` | `dpopKeyMissing` | |
755+
| `DPOP_NOT_CONFIGURED` | `DPOP_NOT_CONFIGURED` | `dpopNotConfigured` | |
756+
| `DPOP_KEY_MISMATCH` | `DPOP_KEY_MISMATCH` | `dpopKeyMismatch` | |
747757

748758
### WebAuth errors
749759

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ dependencies {
9696
implementation "com.facebook.react:react-android"
9797
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
9898
implementation "androidx.browser:browser:1.2.0"
99-
implementation 'com.auth0.android:auth0:3.14.0'
99+
implementation 'com.auth0.android:auth0:3.15.0'
100100
}
101101

102102
if (isNewArchitectureEnabled()) {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
8585
CredentialsManagerException.BIOMETRICS_INVALID_USER to "BIOMETRICS_INVALID_USER",
8686
CredentialsManagerException.BIOMETRIC_AUTHENTICATION_FAILED to "BIOMETRIC_AUTHENTICATION_FAILED",
8787
CredentialsManagerException.API_ERROR to "API_ERROR",
88-
CredentialsManagerException.NO_NETWORK to "NO_NETWORK"
88+
CredentialsManagerException.NO_NETWORK to "NO_NETWORK",
89+
CredentialsManagerException.DPOP_KEY_MISSING to "DPOP_KEY_MISSING",
90+
CredentialsManagerException.DPOP_NOT_CONFIGURED to "DPOP_NOT_CONFIGURED",
91+
CredentialsManagerException.DPOP_KEY_MISMATCH to "DPOP_KEY_MISMATCH"
8992
)
9093
// DPoP enabled by default
9194
private var useDPoP: Boolean = true

ios/NativeBridge.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ extension CredentialsManagerError {
518518
code = "REVOKE_FAILED"
519519
}
520520
case CredentialsManagerError.largeMinTTL: code = "LARGE_MIN_TTL"
521+
case CredentialsManagerError.dpopKeyMissing: code = "DPOP_KEY_MISSING"
522+
case CredentialsManagerError.dpopKeyMismatch: code = "DPOP_KEY_MISMATCH"
523+
case CredentialsManagerError.dpopNotConfigured: code = "DPOP_NOT_CONFIGURED"
521524
default: code = "UNKNOWN"
522525
}
523526
return code

src/core/models/CredentialsManagerError.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export const CredentialsManagerErrorCodes = {
6161
INCOMPATIBLE_DEVICE: 'INCOMPATIBLE_DEVICE',
6262
/** Cryptographic operation failed */
6363
CRYPTO_EXCEPTION: 'CRYPTO_EXCEPTION',
64+
/** DPoP key pair is no longer available in the device keystore/keychain - re-authentication required */
65+
DPOP_KEY_MISSING: 'DPOP_KEY_MISSING',
66+
/** Credentials are DPoP-bound but the client was not configured with DPoP */
67+
DPOP_NOT_CONFIGURED: 'DPOP_NOT_CONFIGURED',
68+
/** Current DPoP key pair does not match the one used when credentials were saved - re-authentication required */
69+
DPOP_KEY_MISMATCH: 'DPOP_KEY_MISMATCH',
6470
/** Unknown or uncategorized error */
6571
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
6672
} as const;
@@ -80,6 +86,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
8086
NO_NETWORK: CredentialsManagerErrorCodes.NO_NETWORK,
8187
API_ERROR: CredentialsManagerErrorCodes.API_ERROR,
8288

89+
// --- DPoP credential state errors ---
90+
DPOP_KEY_MISSING: CredentialsManagerErrorCodes.DPOP_KEY_MISSING,
91+
DPOP_NOT_CONFIGURED: CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED,
92+
DPOP_KEY_MISMATCH: CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH,
93+
8394
// --- API Credentials (MRRT) specific codes ---
8495
API_EXCHANGE_FAILED: CredentialsManagerErrorCodes.API_EXCHANGE_FAILED,
8596
// --- Web (@auth0/auth0-spa-js) mappings ---
@@ -175,6 +186,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
175186
* - `NO_NETWORK`: Network connectivity issue
176187
* - `API_ERROR`: Generic API error
177188
*
189+
* ### DPoP Credential State:
190+
* - `DPOP_KEY_MISSING`: DPoP key pair no longer available in keystore/keychain
191+
* - `DPOP_NOT_CONFIGURED`: Credentials are DPoP-bound but client not configured with DPoP
192+
* - `DPOP_KEY_MISMATCH`: Current DPoP key pair doesn't match the one used when credentials were saved
193+
*
178194
* ### Biometric Authentication:
179195
* - `BIOMETRICS_FAILED`: Biometric authentication failed
180196
* - `INCOMPATIBLE_DEVICE`: Device incompatible with secure storage
@@ -285,6 +301,9 @@ export class CredentialsManagerError extends AuthError {
285301
* - `NO_REFRESH_TOKEN`: Refresh token is not available
286302
* - `RENEW_FAILED`: Token renewal failed
287303
* - `API_EXCHANGE_FAILED`: API credentials exchange failed (MRRT)
304+
* - `DPOP_KEY_MISSING`: DPoP key pair no longer in keystore/keychain
305+
* - `DPOP_NOT_CONFIGURED`: Credentials DPoP-bound but client not configured
306+
* - `DPOP_KEY_MISMATCH`: DPoP key pair doesn't match saved credentials
288307
* - `STORE_FAILED`: Failed to store credentials
289308
* - `REVOKE_FAILED`: Failed to revoke refresh token
290309
* - `LARGE_MIN_TTL`: Requested minimum TTL exceeds token lifetime

src/core/models/__tests__/ErrorCodes.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,20 @@ describe('Error Code Constants', () => {
108108
'CRYPTO_EXCEPTION'
109109
);
110110
expect(CredentialsManagerErrorCodes.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');
111+
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISSING).toBe(
112+
'DPOP_KEY_MISSING'
113+
);
114+
expect(CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED).toBe(
115+
'DPOP_NOT_CONFIGURED'
116+
);
117+
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH).toBe(
118+
'DPOP_KEY_MISMATCH'
119+
);
111120
});
112121

113-
it('should have exactly 15 error codes', () => {
122+
it('should have exactly 18 error codes', () => {
114123
const keys = Object.keys(CredentialsManagerErrorCodes);
115-
expect(keys).toHaveLength(15);
124+
expect(keys).toHaveLength(18);
116125
});
117126

118127
it('should be immutable (as const)', () => {

0 commit comments

Comments
 (0)