@@ -14,14 +14,19 @@ import java.security.SignatureException
1414import java.security.interfaces.ECPublicKey
1515import java.util.UUID
1616
17- public interface SenderConstraining <T > {
18-
19- public fun enableDPoP (context : Context ): T
20-
21- }
2217
18+ /* *
19+ * Data class returning the value that needs to be added to the request for the `Authorization` and `DPoP` headers.
20+ * @param authorizationHeader value for the `Authorization` header key
21+ * @param dpopProof value for the `DPoP header key . This will be generated only for DPoP requests
22+ */
2323public data class HeaderData (val authorizationHeader : String , val dpopProof : String? )
2424
25+
26+ /* *
27+ * Util class for securing requests with DPoP (Demonstrating Proof of Possession) as described in
28+ * [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449).
29+ */
2530public object DPoPProvider {
2631
2732 private const val TAG = " DPoPManager"
@@ -36,6 +41,27 @@ public object DPoPProvider {
3641 public var auth0Nonce: String? = null
3742 private set
3843
44+ /* *
45+ * This method constructs a DPoP proof JWT that includes the HTTP method, URL, and an optional access token and nonce.
46+ *
47+ * ```kotlin
48+ *
49+ * try {
50+ * DPoPProvider.generateProof("{url}", "POST")?.let {
51+ * // Add to the URL request header
52+ * }
53+ * } catch (exception: DPoPException) {
54+ * Log.e(TAG, "Error generating DPoP proof: ${exception.stackTraceToString()}")
55+ * }
56+ *
57+ * ```
58+ *
59+ * @param httpUrl The URL of the HTTP request for which the DPoP proof is being generated.
60+ * @param httpMethod The HTTP method (e.g., "GET", "POST") of the request.
61+ * @param accessToken An optional access token to be included in the proof. If provided, it will be hashed and included in the payload.
62+ * @param nonce An optional nonce value to be included in the proof. This can be used to prevent replay attacks.
63+ * @throws DPoPException if there is an error generating the DPoP proof or accessing the key pair.
64+ */
3965 @Throws(DPoPException ::class )
4066 public fun generateProof (
4167 httpUrl : String ,
@@ -86,11 +112,44 @@ public object DPoPProvider {
86112 return " $headerEncoded .$payloadEncoded .${signature} "
87113 }
88114
115+ /* *
116+ * Method to clear the DPoP key pair from the keystore. It must be called when the user logs out from a session
117+ * to prevent reuse of the key pair in subsequent sessions.
118+ *
119+ * ```kotlin
120+ *
121+ * try {
122+ * DPoPProvider.clearKeyPair()
123+ * } catch (exception: DPoPException) {
124+ * Log.e(TAG,"Error clearing the key pair from the keystore: ${exception.stackTraceToString()}")
125+ * }
126+ *
127+ * ```
128+ * **Note** : It is the developers responsibility to invoke this method to clear the keystore when logging out a session.
129+ * @throws DPoPException if there is an error deleting the key pair.
130+ */
89131 @Throws(DPoPException ::class )
90132 public fun clearKeyPair () {
91133 keyStore.deleteKeyPair()
92134 }
93135
136+ /* *
137+ * Method to get the public key in JWK format. This is used to generate the `jwk` field in the DPoP proof header.
138+ *
139+ * ```kotlin
140+ *
141+ * try {
142+ * val publicKeyJWK = DPoPProvider.getPublicKeyJWK()
143+ * Log.d(TAG, "Public Key JWK: $publicKeyJWK")
144+ * } catch (exception: DPoPException) {
145+ * Log.e(TAG,"Error getting public key JWK: ${exception.stackTraceToString()}")
146+ * }
147+ *
148+ * ```
149+ *
150+ * @return The public key in JWK format or null if the key pair is not present.
151+ * @throws DPoPException if there is an error accessing the key pair.
152+ */
94153 @Throws(DPoPException ::class )
95154 public fun getPublicKeyJWK (): String? {
96155 if (! keyStore.hasKeyPair()) {
@@ -108,6 +167,22 @@ public object DPoPProvider {
108167 return createSHA256Hash(jwkJson.toString())
109168 }
110169
170+ /* *
171+ * Generates a new key pair for DPoP if it does not already exist. This should be called before making any requests that require DPoP proof.
172+ *
173+ * ```kotlin
174+ *
175+ * try {
176+ * DPoPProvider.generateKeyPair(context)
177+ * } catch (exception: DPoPException) {
178+ * Log.e(TAG,"Error generating key pair: ${exception.stackTraceToString()}")
179+ * }
180+ *
181+ * ```
182+ *
183+ * @param context The application context used to access the keystore.
184+ * @throws DPoPException if there is an error generating the key pair or accessing the keystore.
185+ */
111186 @Throws(DPoPException ::class )
112187 public fun generateKeyPair (context : Context ) {
113188 if (keyStore.hasKeyPair()) {
@@ -116,6 +191,37 @@ public object DPoPProvider {
116191 keyStore.generateKeyPair(context)
117192 }
118193
194+ /* *
195+ * Generates the header data for a request that requires DPoP proof of possession. The `Authorization` header value is created
196+ * using the access token and token type. The `DPoP` header value contains the generated DPoP proof
197+ *
198+ * ```kotlin
199+ *
200+ * try {
201+ * val headerData = DPoPProvider.getHeaderData(
202+ * "{POST}",
203+ * "{request_url}",
204+ * "{access_token}",
205+ * "{DPoP}",
206+ * "{nonce_value}"
207+ * )
208+ * addHeader("Authorization", headerData.authorizationHeader) //Adding to request header
209+ * headerData.dpopProof?.let {
210+ * addHeader("DPoP", it)
211+ * }
212+ * } catch (exception: DPoPException) {
213+ * Log.e(TAG, "Error generating DPoP proof: ${exception.stackTraceToString()}")
214+ * }
215+ *
216+ * ```
217+ *
218+ * @param httpMethod Method type of the request
219+ * @param httpUrl Url of the request
220+ * @param accessToken Access token to be included in the `Authorization` header
221+ * @param tokenType Either `DPoP` or `Bearer`
222+ * @param nonce Optional nonce value to be used in the proof
223+ * @throws DPoPException if there is an error generating the DPoP proof or accessing the key pair
224+ */
119225 @Throws(DPoPException ::class )
120226 public fun getHeaderData (
121227 httpMethod : String ,
@@ -130,11 +236,41 @@ public object DPoPProvider {
130236 return HeaderData (token, proof)
131237 }
132238
239+ /* *
240+ * Checks if the given [Response] indicates that a nonce is required for DPoP requests.
241+ * This is typically used to determine if the request needs to be retried with a nonce.
242+ *
243+ * ```kotlin
244+ *
245+ * if (DPoPProvider.isNonceRequiredError(response)) {
246+ * // Handle nonce required error
247+ * }
248+ *
249+ * ```
250+ *
251+ * @param response The HTTP response to check for nonce requirement.
252+ * @return True if the response indicates that a nonce is required, false otherwise.
253+ */
133254 public fun isNonceRequiredError (response : Response ): Boolean {
134255 return (response.code == 400 && response.getErrorBody().errorCode == NONCE_REQUIRED_ERROR ) ||
135256 (response.code == 401 && isResourceServerNonceError(response))
136257 }
137258
259+ /* *
260+ * Stores the nonce value from the Okhttp3 [Response] headers.
261+ *
262+ * ```kotlin
263+ *
264+ * try {
265+ * DPoPProvider.storeNonce(response)
266+ * } catch (exception: Exception) {
267+ * Log.e(TAG, "Error storing nonce: ${exception.stackTraceToString()}")
268+ * }
269+ *
270+ * ```
271+ *
272+ * @param response The HTTP response containing the nonce header.
273+ */
138274 public fun storeNonce (response : Response ) {
139275 auth0Nonce = response.headers[NONCE_HEADER ]
140276 }
0 commit comments