Skip to content

Commit 7413b06

Browse files
committed
Merge branch 'main' of https://github.com/auth0/Auth0.Android into SDK-6075
2 parents 28f059c + 8b23be9 commit 7413b06

File tree

8 files changed

+118
-33
lines changed

8 files changed

+118
-33
lines changed

EXAMPLES.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,8 @@ To sign up a user with passkey
10321032
try {
10331033
val challenge = authenticationApiClient.signupWithPasskey(
10341034
"{user-data}",
1035-
"{realm}"
1035+
"{realm}",
1036+
"{organization-id}"
10361037
).await()
10371038

10381039
//Use CredentialManager to create public key credentials
@@ -1048,7 +1049,7 @@ try {
10481049
)
10491050

10501051
val userCredential = authenticationApiClient.signinWithPasskey(
1051-
challenge.authSession, authRequest, "{realm}"
1052+
challenge.authSession, authRequest, "{realm}" , "{organization-id}"
10521053
)
10531054
.validateClaims()
10541055
.await()
@@ -1060,7 +1061,7 @@ try {
10601061
<summary>Using Java</summary>
10611062

10621063
```java
1063-
authenticationAPIClient.signupWithPasskey("{user-data}", "{realm}")
1064+
authenticationAPIClient.signupWithPasskey("{user-data}", "{realm}","{organization-id}")
10641065
.start(new Callback<PasskeyRegistrationChallenge, AuthenticationException>() {
10651066
@Override
10661067
public void onSuccess(PasskeyRegistrationChallenge result) {
@@ -1078,7 +1079,7 @@ try {
10781079
PublicKeyCredentials.class);
10791080

10801081
authenticationAPIClient.signinWithPasskey(result.getAuthSession(),
1081-
credentials, "{realm}")
1082+
credentials, "{realm}","{organization-id}")
10821083
.start(new Callback<Credentials, AuthenticationException>() {
10831084
@Override
10841085
public void onSuccess(Credentials result) {}
@@ -1104,7 +1105,7 @@ To sign in a user with passkey
11041105
try {
11051106

11061107
val challenge =
1107-
authenticationApiClient.passkeyChallenge("{realm}")
1108+
authenticationApiClient.passkeyChallenge("{realm}","{organization-id}")
11081109
.await()
11091110

11101111
//Use CredentialManager to create public key credentials
@@ -1122,7 +1123,8 @@ try {
11221123
val userCredential = authenticationApiClient.signinWithPasskey(
11231124
challenge.authSession,
11241125
authRequest,
1125-
"{realm}"
1126+
"{realm}",
1127+
"{organization-id}"
11261128
)
11271129
.validateClaims()
11281130
.await()
@@ -1138,7 +1140,7 @@ try {
11381140
<summary>Using Java</summary>
11391141

11401142
```java
1141-
authenticationAPIClient.passkeyChallenge("realm")
1143+
authenticationAPIClient.passkeyChallenge("realm","{organization-id}")
11421144
.start(new Callback<PasskeyChallenge, AuthenticationException>() {
11431145
@Override
11441146
public void onSuccess(PasskeyChallenge result) {
@@ -1158,7 +1160,7 @@ authenticationAPIClient.passkeyChallenge("realm")
11581160
responseJson,
11591161
PublicKeyCredentials.class
11601162
);
1161-
authenticationAPIClient.signinWithPasskey(result.getAuthSession(), publicKeyCredentials,"{realm}")
1163+
authenticationAPIClient.signinWithPasskey(result.getAuthSession(), publicKeyCredentials,"{realm}","{organization-id}")
11621164
.start(new Callback<Credentials, AuthenticationException>() {
11631165
@Override
11641166
public void onSuccess(Credentials result) {}

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

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
166166
* Example usage:
167167
*
168168
* ```
169-
* client.signinWithPasskey("{authSession}", "{authResponse}","{realm}")
169+
* client.signinWithPasskey("{authSession}", "{authResponse}","{realm}","${organization}")
170170
* .validateClaims() //mandatory
171171
* .setScope("{scope}")
172172
* .start(object: Callback<Credentials, AuthenticationException> {
@@ -178,17 +178,20 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
178178
* @param authSession the auth session received from the server as part of the public key challenge request.
179179
* @param authResponse the [PublicKeyCredentials] authentication response
180180
* @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant
181+
* @param organization id of the organization to be associated with the user while signing in
181182
* @return a request to configure and start that will yield [Credentials]
182183
*/
183184
public fun signinWithPasskey(
184185
authSession: String,
185186
authResponse: PublicKeyCredentials,
186-
realm: String? = null
187+
realm: String? = null,
188+
organization: String? = null,
187189
): AuthenticationRequest {
188190
val params = ParameterBuilder.newBuilder().apply {
189191
setGrantType(ParameterBuilder.GRANT_TYPE_PASSKEY)
190192
set(AUTH_SESSION_KEY, authSession)
191193
realm?.let { setRealm(it) }
194+
organization?.let { set(ORGANIZATION_KEY, organization) }
192195
}.asDictionary()
193196

194197
return loginWithToken(params)
@@ -210,7 +213,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
210213
* Example usage:
211214
*
212215
* ```
213-
* client.signinWithPasskey("{authSession}", "{authResponse}","{realm}")
216+
* client.signinWithPasskey("{authSession}", "{authResponse}","{realm}","{organization}")
214217
* .validateClaims() //mandatory
215218
* .setScope("{scope}")
216219
* .start(object: Callback<Credentials, AuthenticationException> {
@@ -222,18 +225,20 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
222225
* @param authSession the auth session received from the server as part of the public key challenge request.
223226
* @param authResponse the public key credential authentication response in JSON string format that follows the standard webauthn json format
224227
* @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant
228+
* @param organization id of the organization to be associated with the user while signing in
225229
* @return a request to configure and start that will yield [Credentials]
226230
*/
227231
public fun signinWithPasskey(
228232
authSession: String,
229233
authResponse: String,
230-
realm: String? = null
234+
realm: String? = null,
235+
organization: String? = null,
231236
): AuthenticationRequest {
232237
val publicKeyCredentials = gson.fromJson(
233238
authResponse,
234239
PublicKeyCredentials::class.java
235240
)
236-
return signinWithPasskey(authSession, publicKeyCredentials, realm)
241+
return signinWithPasskey(authSession, publicKeyCredentials, realm, organization)
237242
}
238243

239244

@@ -247,7 +252,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
247252
*
248253
*
249254
* ```
250-
* client.signupWithPasskey("{userData}","{realm}")
255+
* client.signupWithPasskey("{userData}","{realm}","{organization}")
251256
* .addParameter("scope","scope")
252257
* .start(object: Callback<PasskeyRegistration, AuthenticationException> {
253258
* override fun onSuccess(result: PasskeyRegistration) { }
@@ -257,11 +262,13 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
257262
*
258263
* @param userData user information of the client
259264
* @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant
265+
* @param organization id of the organization to be associated with the user while signing up
260266
* @return a request to configure and start that will yield [PasskeyRegistrationChallenge]
261267
*/
262268
public fun signupWithPasskey(
263269
userData: UserData,
264-
realm: String? = null
270+
realm: String? = null,
271+
organization: String? = null
265272
): Request<PasskeyRegistrationChallenge, AuthenticationException> {
266273
val user = gson.toJsonTree(userData)
267274
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
@@ -272,6 +279,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
272279
val params = ParameterBuilder.newBuilder().apply {
273280
setClientId(clientId)
274281
realm?.let { setRealm(it) }
282+
organization?.let { set(ORGANIZATION_KEY, it) }
275283
}.asDictionary()
276284

277285
val passkeyRegistrationChallengeAdapter: JsonAdapter<PasskeyRegistrationChallenge> =
@@ -293,18 +301,20 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
293301
* Example usage:
294302
*
295303
* ```
296-
* client.passkeyChallenge("{realm}")
304+
* client.passkeyChallenge("{realm}", "{organization}")
297305
* .start(object: Callback<PasskeyChallenge, AuthenticationException> {
298306
* override fun onSuccess(result: PasskeyChallenge) { }
299307
* override fun onFailure(error: AuthenticationException) { }
300308
* })
301309
* ```
302310
*
303311
* @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant
312+
* @param organization id of the organization to be associated with the user while signing in
304313
* @return a request to configure and start that will yield [PasskeyChallenge]
305314
*/
306315
public fun passkeyChallenge(
307-
realm: String? = null
316+
realm: String? = null,
317+
organization: String? = null
308318
): Request<PasskeyChallenge, AuthenticationException> {
309319
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
310320
.addPathSegment(PASSKEY_PATH)
@@ -314,6 +324,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
314324
val parameters = ParameterBuilder.newBuilder().apply {
315325
setClientId(clientId)
316326
realm?.let { setRealm(it) }
327+
organization?.let { set(ORGANIZATION_KEY, organization) }
317328
}.asDictionary()
318329

319330
val passkeyChallengeAdapter: JsonAdapter<PasskeyChallenge> = GsonAdapter(
@@ -1054,7 +1065,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
10541065
private const val RECOVERY_CODE_KEY = "recovery_code"
10551066
private const val SUBJECT_TOKEN_KEY = "subject_token"
10561067
private const val SUBJECT_TOKEN_TYPE_KEY = "subject_token_type"
1057-
private const val REQUESTED_TOKEN_TYPE_KEY = "requested_token_type"
1068+
private const val ORGANIZATION_KEY = "organization"
10581069
private const val USER_METADATA_KEY = "user_metadata"
10591070
private const val AUTH_SESSION_KEY = "auth_session"
10601071
private const val AUTH_RESPONSE_KEY = "authn_response"

auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ package com.auth0.android.authentication.storage
33
import androidx.annotation.VisibleForTesting
44
import com.auth0.android.authentication.AuthenticationAPIClient
55
import com.auth0.android.callback.Callback
6+
import com.auth0.android.request.internal.GsonProvider
7+
import com.auth0.android.request.internal.Jwt
68
import com.auth0.android.result.APICredentials
79
import com.auth0.android.result.Credentials
810
import com.auth0.android.result.SSOCredentials
11+
import com.auth0.android.result.UserProfile
912
import com.auth0.android.util.Clock
1013
import java.util.*
14+
import kotlin.collections.component1
15+
import kotlin.collections.component2
1116

1217
/**
1318
* Base class meant to abstract common logic across Credentials Manager implementations.
@@ -38,6 +43,7 @@ public abstract class BaseCredentialsManager internal constructor(
3843
callback: Callback<SSOCredentials, CredentialsManagerException>
3944
)
4045

46+
4147
public abstract fun getSsoCredentials(
4248
callback: Callback<SSOCredentials, CredentialsManagerException>
4349
)
@@ -136,6 +142,8 @@ public abstract class BaseCredentialsManager internal constructor(
136142
headers: Map<String, String> = emptyMap()
137143
): APICredentials
138144

145+
public abstract val userProfile: UserProfile?
146+
139147
public abstract fun clearCredentials()
140148
public abstract fun clearApiCredentials(audience: String)
141149
public abstract fun hasValidCredentials(): Boolean

auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
package com.auth0.android.authentication.storage
22

33
import android.text.TextUtils
4+
import android.util.Base64
45
import android.util.Log
56
import androidx.annotation.VisibleForTesting
67
import com.auth0.android.authentication.AuthenticationAPIClient
78
import com.auth0.android.authentication.AuthenticationException
9+
import com.auth0.android.authentication.storage.SecureCredentialsManager.Companion.KEY_CREDENTIALS
810
import com.auth0.android.callback.Callback
911
import com.auth0.android.request.internal.GsonProvider
12+
import com.auth0.android.request.internal.Jwt
1013
import com.auth0.android.result.APICredentials
1114
import com.auth0.android.result.Credentials
15+
import com.auth0.android.result.OptionalCredentials
1216
import com.auth0.android.result.SSOCredentials
17+
import com.auth0.android.result.UserProfile
1318
import com.auth0.android.result.toAPICredentials
1419
import com.google.gson.Gson
1520
import kotlinx.coroutines.suspendCancellableCoroutine
1621
import java.util.*
1722
import java.util.concurrent.Executor
1823
import java.util.concurrent.Executors
24+
import kotlin.collections.component1
25+
import kotlin.collections.component2
1926
import kotlin.coroutines.resume
2027
import kotlin.coroutines.resumeWithException
2128

@@ -44,6 +51,18 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
4451
Executors.newSingleThreadExecutor()
4552
)
4653

54+
public override val userProfile: UserProfile?
55+
get() {
56+
val idToken = storage.retrieveString(KEY_ID_TOKEN)
57+
58+
if (idToken.isNullOrBlank()) {
59+
return null
60+
}
61+
val (_, payload) = Jwt.splitToken(idToken)
62+
val gson = GsonProvider.gson
63+
return gson.fromJson(Jwt.decodeBase64(payload), UserProfile::class.java)
64+
}
65+
4766
/**
4867
* Stores the given credentials in the storage. Must have an access_token or id_token and a expires_in value.
4968
*

auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,23 @@ import com.auth0.android.authentication.AuthenticationAPIClient
1111
import com.auth0.android.authentication.AuthenticationException
1212
import com.auth0.android.callback.Callback
1313
import com.auth0.android.request.internal.GsonProvider
14+
import com.auth0.android.request.internal.Jwt
1415
import com.auth0.android.result.APICredentials
1516
import com.auth0.android.result.Credentials
1617
import com.auth0.android.result.OptionalCredentials
1718
import com.auth0.android.result.SSOCredentials
19+
import com.auth0.android.result.UserProfile
1820
import com.auth0.android.result.toAPICredentials
1921
import com.google.gson.Gson
22+
import kotlinx.coroutines.CoroutineScope
23+
import kotlinx.coroutines.GlobalScope
24+
import kotlinx.coroutines.launch
2025
import kotlinx.coroutines.suspendCancellableCoroutine
2126
import java.lang.ref.WeakReference
2227
import java.util.*
2328
import java.util.concurrent.Executor
29+
import kotlin.collections.component1
30+
import kotlin.collections.component2
2431
import kotlin.coroutines.resume
2532
import kotlin.coroutines.resumeWithException
2633

@@ -250,6 +257,16 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
250257
}
251258
}
252259

260+
public override val userProfile: UserProfile?
261+
get() {
262+
val credentials: Credentials? = getExistingCredentials()
263+
// Handle null credentials gracefully
264+
if (credentials == null) {
265+
return null
266+
}
267+
return credentials.user
268+
}
269+
253270
/**
254271
* Creates a new request to exchange a refresh token for a session transfer token that can be used to perform web single sign-on.
255272
*

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ public class AuthenticationAPIClientTest {
193193
val callback = MockAuthenticationCallback<Credentials>()
194194
val auth0 = auth0
195195
val client = AuthenticationAPIClient(auth0)
196-
client.signinWithPasskey("auth-session", mock<PublicKeyCredentials>(), MY_CONNECTION)
196+
client.signinWithPasskey("auth-session", mock<PublicKeyCredentials>(), MY_CONNECTION,
197+
"testOrganisation")
197198
.start(callback)
198199
ShadowLooper.idleMainLooper()
199200
assertThat(
@@ -216,6 +217,7 @@ public class AuthenticationAPIClientTest {
216217
)
217218
assertThat(body, Matchers.hasKey("authn_response"))
218219
assertThat(body, Matchers.hasEntry("auth_session", "auth-session"))
220+
assertThat(body, Matchers.hasEntry("organization", "testOrganisation"))
219221
}
220222

221223
@Test
@@ -225,7 +227,8 @@ public class AuthenticationAPIClientTest {
225227
val client = AuthenticationAPIClient(auth0)
226228
val registrationResponse = client.signupWithPasskey(
227229
mock(),
228-
MY_CONNECTION
230+
MY_CONNECTION,
231+
"testOrganization"
229232
)
230233
.execute()
231234
val request = mockAPI.takeRequest()
@@ -238,6 +241,7 @@ public class AuthenticationAPIClientTest {
238241
assertThat(request.path, Matchers.equalTo("/passkey/register"))
239242
assertThat(body, Matchers.hasEntry("client_id", CLIENT_ID))
240243
assertThat(body, Matchers.hasEntry("realm", MY_CONNECTION))
244+
assertThat(body, Matchers.hasEntry("organization", "testOrganization"))
241245
assertThat(body, Matchers.hasKey("user_profile"))
242246
assertThat(registrationResponse, Matchers.`is`(Matchers.notNullValue()))
243247
assertThat(registrationResponse.authSession, Matchers.comparesEqualTo(SESSION_ID))
@@ -248,7 +252,7 @@ public class AuthenticationAPIClientTest {
248252
mockAPI.willReturnSuccessfulPasskeyChallenge()
249253
val auth0 = auth0
250254
val client = AuthenticationAPIClient(auth0)
251-
val challengeResponse = client.passkeyChallenge(MY_CONNECTION)
255+
val challengeResponse = client.passkeyChallenge(MY_CONNECTION, "testOrganization")
252256
.execute()
253257
val request = mockAPI.takeRequest()
254258
assertThat(
@@ -260,6 +264,7 @@ public class AuthenticationAPIClientTest {
260264
assertThat(request.path, Matchers.equalTo("/passkey/challenge"))
261265
assertThat(body, Matchers.hasEntry("client_id", CLIENT_ID))
262266
assertThat(body, Matchers.hasEntry("realm", MY_CONNECTION))
267+
assertThat(body, Matchers.hasEntry("organization", "testOrganization"))
263268
assertThat(challengeResponse, Matchers.`is`(Matchers.notNullValue()))
264269
assertThat(challengeResponse.authSession, Matchers.comparesEqualTo(SESSION_ID))
265270

@@ -2749,7 +2754,6 @@ public class AuthenticationAPIClientTest {
27492754
private const val FIRST_NAME = "John"
27502755
private const val LAST_NAME = "Doe"
27512756
private const val COMPANY = "Auth0"
2752-
private const val OPENID = "openid"
27532757
private const val DEFAULT_LOCALE_IF_MISSING = "en_US"
27542758
}
27552759
}

0 commit comments

Comments
 (0)