Skip to content

Commit dfddd77

Browse files
committed
Added case to generate key pair for embedded login flow
1 parent 1da729d commit dfddd77

File tree

10 files changed

+71
-62
lines changed

10 files changed

+71
-62
lines changed

auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.android.authentication
22

3+
import android.content.Context
34
import androidx.annotation.VisibleForTesting
45
import com.auth0.android.Auth0
56
import com.auth0.android.Auth0Exception
@@ -67,8 +68,8 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
6768
/**
6869
* Enable DPoP for this client.
6970
*/
70-
public override fun useDPoP(): AuthenticationAPIClient {
71-
dPoP = DPoP()
71+
public override fun useDPoP(context: Context): AuthenticationAPIClient {
72+
dPoP = DPoP(context)
7273
return this
7374
}
7475

auth0/src/main/java/com/auth0/android/dpop/DPoP.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ public data class HeaderData(val authorizationHeader: String, val dpopProof: Str
2222
* Class for securing requests with DPoP (Demonstrating Proof of Possession) as described in
2323
* [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449).
2424
*/
25-
public class DPoP {
25+
public class DPoP(private val context: Context) {
2626

2727
/**
28-
* Determines whether a DPoP proof should be generated for the given URL and parameters.
28+
* Determines whether a DPoP proof should be generated for the given URL and parameters. The proof should
29+
* only be generated for the `token` endpoint for a fresh login scenario or when a key-pair already exists.
2930
*
3031
* @param url The URL of the request
3132
* @param parameters The request parameters as a map
@@ -77,7 +78,7 @@ public class DPoP {
7778
* @throws DPoPException if there is an error generating the key pair or accessing the keystore.
7879
*/
7980
@Throws(DPoPException::class)
80-
internal fun generateKeyPair(context: Context) {
81+
internal fun generateKeyPair() {
8182
DPoPUtil.generateKeyPair(context)
8283
}
8384

@@ -89,8 +90,8 @@ public class DPoP {
8990
* @throws DPoPException if there is an error accessing the key pair.
9091
*/
9192
@Throws(DPoPException::class)
92-
internal fun getPublicKeyJWK(context: Context): String? {
93-
generateKeyPair(context)
93+
internal fun getPublicKeyJWK(): String? {
94+
generateKeyPair()
9495
return DPoPUtil.getPublicKeyJWK()
9596
}
9697

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.auth0.android.dpop
22

3+
import android.content.Context
4+
35
/**
46
* Interface for SenderConstraining
57
*/
@@ -8,5 +10,5 @@ public interface SenderConstraining<T : SenderConstraining<T>> {
810
/**
911
* Method to enable DPoP in the request.
1012
*/
11-
public fun useDPoP(): T
13+
public fun useDPoP(context: Context): T
1214
}

auth0/src/main/java/com/auth0/android/provider/OAuthManager.kt

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ internal class OAuthManager(
6363

6464
fun startAuthentication(context: Context, redirectUri: String, requestCode: Int) {
6565
OidcUtils.includeDefaultScope(parameters)
66-
addPKCEParameters(parameters, redirectUri, headers)
66+
addPKCEParameters(parameters, redirectUri, headers, context)
6767
addClientParameters(parameters, redirectUri)
6868
try {
69-
addDPoPJWKParameters(parameters, context)
69+
addDPoPJWKParameters(parameters)
7070
} catch (ex: DPoPException) {
7171
callback.onFailure(
7272
AuthenticationException(
@@ -273,9 +273,10 @@ internal class OAuthManager(
273273
private fun addPKCEParameters(
274274
parameters: MutableMap<String, String>,
275275
redirectUri: String,
276-
headers: Map<String, String>
276+
headers: Map<String, String>,
277+
context: Context
277278
) {
278-
createPKCE(redirectUri, headers)
279+
createPKCE(redirectUri, headers,context)
279280
val codeChallenge = pkce!!.codeChallenge
280281
parameters[KEY_CODE_CHALLENGE] = codeChallenge
281282
parameters[KEY_CODE_CHALLENGE_METHOD] = METHOD_SHA_256
@@ -295,14 +296,18 @@ internal class OAuthManager(
295296
parameters[KEY_REDIRECT_URI] = redirectUri
296297
}
297298

298-
private fun createPKCE(redirectUri: String, headers: Map<String, String>) {
299+
private fun createPKCE(redirectUri: String, headers: Map<String, String>,context: Context) {
299300
if (pkce == null) {
301+
// Enable DPoP on the AuthenticationClient if DPoP is set in the WebAuthProvider class
302+
dPoP?.let {
303+
apiClient.useDPoP(context)
304+
}
300305
pkce = PKCE(apiClient, redirectUri, headers)
301306
}
302307
}
303308

304-
private fun addDPoPJWKParameters(parameters: MutableMap<String, String>, context: Context) {
305-
dPoP?.getPublicKeyJWK(context)?.let {
309+
private fun addDPoPJWKParameters(parameters: MutableMap<String, String>) {
310+
dPoP?.getPublicKeyJWK()?.let {
306311
parameters["dpop_jkt"] = it
307312
}
308313
}
@@ -376,10 +381,6 @@ internal class OAuthManager(
376381
this.parameters = parameters.toMutableMap()
377382
this.parameters[KEY_RESPONSE_TYPE] = RESPONSE_TYPE_CODE
378383
apiClient = AuthenticationAPIClient(account)
379-
// Enable DPoP on the AuthenticationClient if DPoP is set in the WebAuthProvider class
380-
dPoP?.let {
381-
apiClient.useDPoP()
382-
}
383384
this.ctOptions = ctOptions
384385
}
385386
}
@@ -398,7 +399,7 @@ internal fun OAuthManager.Companion.fromState(
398399
setHeaders(
399400
state.headers
400401
)
401-
setPKCE(state.pkce)
402+
setPKCE(state.pkce)
402403
setIdTokenVerificationIssuer(state.idTokenVerificationIssuer)
403404
setIdTokenVerificationLeeway(state.idTokenVerificationLeeway)
404405
}

auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public object WebAuthProvider : SenderConstraining<WebAuthProvider> {
5050
}
5151

5252
// Public methods
53-
public override fun useDPoP(): WebAuthProvider {
54-
dPoP = DPoP()
53+
public override fun useDPoP(context: Context): WebAuthProvider {
54+
dPoP = DPoP(context)
5555
return this
5656
}
5757

auth0/src/main/java/com/auth0/android/request/internal/BaseRequest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ internal open class BaseRequest<T, U : Auth0Exception>(
129129
val response: ServerResponse
130130
try {
131131
if (dPoP?.shouldGenerateProof(url, options.parameters) == true) {
132+
dPoP.generateKeyPair()
132133
dPoP.generateProof(url, method, options.headers)?.let {
133134
options.headers[DPoPUtil.DPOP_HEADER] = it
134135
}

auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import org.junit.After
4848
import org.junit.Before
4949
import org.junit.Test
5050
import org.junit.runner.RunWith
51-
import org.mockito.Mockito.`when`
5251
import org.robolectric.RobolectricTestRunner
5352
import org.robolectric.annotation.Config
5453
import org.robolectric.shadows.ShadowLooper
@@ -65,11 +64,13 @@ public class AuthenticationAPIClientTest {
6564
private lateinit var gson: Gson
6665
private lateinit var mockAPI: AuthenticationAPIMockServer
6766
private lateinit var mockKeyStore: DPoPKeyStore
67+
private lateinit var mockContext: Context
6868

6969
@Before
7070
public fun setUp() {
7171
mockAPI = AuthenticationAPIMockServer()
7272
mockKeyStore = mock()
73+
mockContext = mock()
7374
val auth0 = auth0
7475
client = AuthenticationAPIClient(auth0)
7576
gson = GsonBuilder().serializeNulls().create()
@@ -2768,7 +2769,7 @@ public class AuthenticationAPIClientTest {
27682769
val callback = MockAuthenticationCallback<Credentials>()
27692770

27702771
// Enable DPoP
2771-
client.useDPoP().login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
2772+
client.useDPoP(mockContext).login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
27722773
.start(callback)
27732774
ShadowLooper.idleMainLooper()
27742775

@@ -2789,7 +2790,7 @@ public class AuthenticationAPIClientTest {
27892790
mockAPI.willReturnSuccessfulLogin()
27902791
val callback = MockAuthenticationCallback<Credentials>()
27912792

2792-
client.useDPoP().login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
2793+
client.useDPoP(mockContext).login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
27932794
.start(callback)
27942795
ShadowLooper.idleMainLooper()
27952796

@@ -2811,7 +2812,7 @@ public class AuthenticationAPIClientTest {
28112812
mockAPI.willReturnSuccessfulLogin()
28122813
val callback = MockAuthenticationCallback<Credentials>()
28132814

2814-
client.useDPoP().token("auth-code", "code-verifier", "http://redirect.uri")
2815+
client.useDPoP(mockContext).token("auth-code", "code-verifier", "http://redirect.uri")
28152816
.start(callback)
28162817
ShadowLooper.idleMainLooper()
28172818

@@ -2852,7 +2853,7 @@ public class AuthenticationAPIClientTest {
28522853
mockAPI.willReturnUserInfo()
28532854
val callback = MockAuthenticationCallback<UserProfile>()
28542855

2855-
client.useDPoP().userInfo("ACCESS_TOKEN", "DPoP")
2856+
client.useDPoP(mockContext).userInfo("ACCESS_TOKEN", "DPoP")
28562857
.start(callback)
28572858
ShadowLooper.idleMainLooper()
28582859

@@ -2896,7 +2897,7 @@ public class AuthenticationAPIClientTest {
28962897
val callback = MockAuthenticationCallback<DatabaseUser>()
28972898

28982899
// DPoP is enabled but signup endpoint should not get DPoP header
2899-
client.useDPoP().createUser(SUPPORT_AUTH0_COM, PASSWORD, SUPPORT, MY_CONNECTION)
2900+
client.useDPoP(mockContext).createUser(SUPPORT_AUTH0_COM, PASSWORD, SUPPORT, MY_CONNECTION)
29002901
.start(callback)
29012902
ShadowLooper.idleMainLooper()
29022903

@@ -2919,7 +2920,7 @@ public class AuthenticationAPIClientTest {
29192920
val callback = MockAuthenticationCallback<Void>()
29202921

29212922
// DPoP is enabled but passwordless endpoint should not get DPoP header
2922-
client.useDPoP().passwordlessWithEmail(SUPPORT_AUTH0_COM, PasswordlessType.CODE)
2923+
client.useDPoP(mockContext).passwordlessWithEmail(SUPPORT_AUTH0_COM, PasswordlessType.CODE)
29232924
.start(callback)
29242925
ShadowLooper.idleMainLooper()
29252926

@@ -2938,7 +2939,7 @@ public class AuthenticationAPIClientTest {
29382939
val callback = MockAuthenticationCallback<Map<String, PublicKey>>()
29392940

29402941
// DPoP is enabled but JWKS endpoint should not get DPoP header
2941-
client.useDPoP().fetchJsonWebKeys()
2942+
client.useDPoP(mockContext).fetchJsonWebKeys()
29422943
.start(callback)
29432944
ShadowLooper.idleMainLooper()
29442945

@@ -2956,7 +2957,7 @@ public class AuthenticationAPIClientTest {
29562957
mockAPI.willReturnSuccessfulLogin()
29572958
val callback = MockAuthenticationCallback<Credentials>()
29582959

2959-
client.useDPoP().customTokenExchange("subject-token-type", "subject-token")
2960+
client.useDPoP(mockContext).customTokenExchange("subject-token-type", "subject-token")
29602961
.start(callback)
29612962
ShadowLooper.idleMainLooper()
29622963

@@ -2983,7 +2984,7 @@ public class AuthenticationAPIClientTest {
29832984
mockAPI.willReturnSuccessfulLogin()
29842985
val callback = MockAuthenticationCallback<SSOCredentials>()
29852986

2986-
client.useDPoP().ssoExchange("refresh-token")
2987+
client.useDPoP(mockContext).ssoExchange("refresh-token")
29872988
.start(callback)
29882989
ShadowLooper.idleMainLooper()
29892990

@@ -3005,7 +3006,7 @@ public class AuthenticationAPIClientTest {
30053006
mockAPI.willReturnSuccessfulLogin()
30063007
val callback = MockAuthenticationCallback<Credentials>()
30073008

3008-
client.useDPoP().login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
3009+
client.useDPoP(mockContext).login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION)
30093010
.start(callback)
30103011
ShadowLooper.idleMainLooper()
30113012

auth0/src/test/java/com/auth0/android/dpop/DPoPTest.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class DPoPTest {
4747
mockResponse = mock()
4848
mockKeyStore = mock()
4949
mockResponseBody = mock()
50-
dPoP = DPoP()
50+
dPoP = DPoP(mockContext)
5151

5252
DPoP._auth0Nonce = null
5353

@@ -158,7 +158,7 @@ public class DPoPTest {
158158
public fun `generateKeyPair should delegate to DPoPUtil`() {
159159
whenever(mockKeyStore.hasKeyPair()).thenReturn(false)
160160

161-
dPoP.generateKeyPair(mockContext)
161+
dPoP.generateKeyPair()
162162

163163
verify(mockKeyStore).generateKeyPair(mockContext)
164164
}
@@ -173,7 +173,7 @@ public class DPoPTest {
173173
)
174174

175175
try {
176-
dPoP.generateKeyPair(mockContext)
176+
dPoP.generateKeyPair()
177177
Assert.fail("Expected DPoPException to be thrown")
178178
} catch (e: DPoPException) {
179179
assertThat(e, `is`(exception))
@@ -186,7 +186,7 @@ public class DPoPTest {
186186
whenever(mockKeyStore.hasKeyPair()).thenReturn(false).thenReturn(true)
187187
whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(fakePrivateKey, fakePublicKey))
188188

189-
val result = dPoP.getPublicKeyJWK(mockContext)
189+
val result = dPoP.getPublicKeyJWK()
190190

191191
verify(mockKeyStore).generateKeyPair(mockContext)
192192
assertThat(result, `is`(testPublicJwkHash))
@@ -197,7 +197,7 @@ public class DPoPTest {
197197
whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
198198
whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(fakePrivateKey, fakePublicKey))
199199

200-
val result = dPoP.getPublicKeyJWK(mockContext)
200+
val result = dPoP.getPublicKeyJWK()
201201

202202
assertThat(result, `is`(testPublicJwkHash))
203203
}
@@ -206,7 +206,7 @@ public class DPoPTest {
206206
public fun `getPublicKeyJWK should return null when key pair generation fails`() {
207207
whenever(mockKeyStore.hasKeyPair()).thenReturn(false).thenReturn(false)
208208

209-
val result = dPoP.getPublicKeyJWK(mockContext)
209+
val result = dPoP.getPublicKeyJWK()
210210

211211
verify(mockKeyStore).generateKeyPair(mockContext)
212212
assertThat(result, `is`(nullValue()))
@@ -504,11 +504,11 @@ public class DPoPTest {
504504
whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(fakePrivateKey, fakePublicKey))
505505

506506
// 1. Generate key pair
507-
dPoP.generateKeyPair(mockContext)
507+
dPoP.generateKeyPair()
508508
verify(mockKeyStore).generateKeyPair(mockContext)
509509

510510
// 2. Get JWK thumbprint
511-
val jwkThumbprint = dPoP.getPublicKeyJWK(mockContext)
511+
val jwkThumbprint = dPoP.getPublicKeyJWK()
512512
assertThat(jwkThumbprint, `is`(testPublicJwkHash))
513513

514514
// 3. Store nonce from response

auth0/src/test/java/com/auth0/android/dpop/DPoPUtilTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ public class DPoPUtilTest {
356356
whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
357357
DPoPUtil.generateKeyPair(mockContext)
358358
verify(mockKeyStore).hasKeyPair()
359-
verify(mockKeyStore, never()).generateKeyPair(any())
359+
verify(mockKeyStore, never()).generateKeyPair(any(), any())
360360
}
361361

362362
@Test

0 commit comments

Comments
 (0)