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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe

private var dPoP: DPoP? = null

/**
* Returns whether DPoP (Demonstrating Proof of Possession) is enabled on this client.
* DPoP is enabled by calling [useDPoP].
*/
public val isDPoPEnabled: Boolean
get() = dPoP != null
Comment on lines +58 to +63
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

isDPoPEnabled is a new public API surface. There are already DPoP-related tests in AuthenticationAPIClientTest; consider adding a small assertion covering isDPoPEnabled (false by default, true after useDPoP(context)) to prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be updated in later PRs


/**
* Creates a new API client instance providing Auth0 account info.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public abstract class BaseCredentialsManager internal constructor(
protected val storage: Storage,
private val jwtDecoder: JWTDecoder
) {

internal companion object {
internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint"
}
Comment on lines +24 to +26
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

KEY_DPOP_THUMBPRINT is introduced but isn’t referenced anywhere in the module yet. If it’s not used in this PR, consider removing it (or adding the corresponding store/retrieve logic) to avoid accumulating unused/unclear storage keys.

Suggested change
internal companion object {
internal const val KEY_DPOP_THUMBPRINT = "com.auth0.dpop_key_thumbprint"
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be updated in later PRs

private var _clock: Clock = ClockImpl()

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class CredentialsManagerException :
API_ERROR,
SSO_EXCHANGE_FAILED,
MFA_REQUIRED,
DPOP_KEY_MISSING,
DPOP_NOT_CONFIGURED,
UNKNOWN_ERROR
Comment on lines 48 to 53
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The PR description says these new exceptions are thrown when DPoP-bound credentials are renewed but the key pair is missing / DPoP isn’t configured; however, there are no call sites in the codebase that ever create/throw CredentialsManagerException(Code.DPOP_KEY_MISSING|DPOP_NOT_CONFIGURED) (they only exist as constants/messages). Either wire these codes into the relevant CredentialsManager/SecureCredentialsManager renewal paths, or adjust the PR description to reflect that this is just adding the enum values/messages for future use.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be updated in later PRs

}

Expand Down Expand Up @@ -159,6 +161,11 @@ public class CredentialsManagerException :
public val MFA_REQUIRED: CredentialsManagerException =
CredentialsManagerException(Code.MFA_REQUIRED)

public val DPOP_KEY_MISSING: CredentialsManagerException =
CredentialsManagerException(Code.DPOP_KEY_MISSING)
public val DPOP_NOT_CONFIGURED: CredentialsManagerException =
CredentialsManagerException(Code.DPOP_NOT_CONFIGURED)

public val UNKNOWN_ERROR: CredentialsManagerException = CredentialsManagerException(Code.UNKNOWN_ERROR)


Expand Down Expand Up @@ -207,6 +214,8 @@ public class CredentialsManagerException :
Code.API_ERROR -> "An error occurred while processing the request."
Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed."
Code.MFA_REQUIRED -> "Multi-factor authentication is required to complete the credential renewal."
Code.DPOP_KEY_MISSING -> "The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the Android KeyStore. Re-authentication is required."
Code.DPOP_NOT_CONFIGURED -> "The stored credentials are DPoP-bound but the AuthenticationAPIClient used by this CredentialsManager was not configured with useDPoP(context). Call AuthenticationAPIClient(auth0).useDPoP(context) and pass the configured client to CredentialsManager."
Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details."
}
}
Expand Down
23 changes: 23 additions & 0 deletions auth0/src/main/java/com/auth0/android/dpop/DPoP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,29 @@ public class DPoP(context: Context) {
return HeaderData(token, proof)
}

/**
* Returns whether a DPoP key pair currently exists in the Android KeyStore.
*
* This can be used to check if DPoP credentials are still available after events
* like device backup/restore or factory reset, which do not preserve KeyStore entries.
*
* ```kotlin
*
* if (!DPoP.hasKeyPair()) {
* // Key was lost — clear stored credentials and re-authenticate
* }
*
* ```
*
* @return true if a DPoP key pair exists in the KeyStore, false otherwise.
* @throws DPoPException if there is an error accessing the KeyStore.
*/
@Throws(DPoPException::class)
@JvmStatic
public fun hasKeyPair(): Boolean {
Comment on lines +219 to +220
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

DPoPUtil.hasKeyPair() it checks if the key exists, not if the key is usable.
in thi PR or in subsequent PR we will be checking if the key is usable or not right?
like below:
keyStore.getCertificate(KEY_ALIAS)?.publicKey ?: return false

So that we can be sure the key is not corrupt or un usable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We check the key is present or not . We will have another check to see if the key is usable or not in later PRs

return DPoPUtil.hasKeyPair()
}

/**
* Method to clear the DPoP key pair from the keystore. It must be called when the user logs out
* to prevent reuse of the key pair in subsequent sessions.
Expand Down
Loading