Skip to content

Commit b7dcbfc

Browse files
authored
Add sign in with google to Shrine (#151)
* Add sign in with google to Shrine * Minor changes * Updated javadocs * Fix rebase conflicts
1 parent 7cdd355 commit b7dcbfc

File tree

14 files changed

+363
-85
lines changed

14 files changed

+363
-85
lines changed

Shrine/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ This section describes how to implement restore credentials
216216

217217
2. Once on a new device, check if there is any restore key present on the device or not (brought to the new device in the process of Backup and Restore)
218218

219-
1. Call `AuthRepository`'s `signInWithPasskeysRequest` method
219+
1. Call `AuthRepository`'s `signInWithPasskeyOrPasswordRequest` method
220220

221221
2. With the PasskeyCreationRequest recieved from the above method, call `CredentialManagerUtils`'s `getRestoreKey` method
222222

223-
3. If there is a RestoreKey present this will return a `GenericCredentialManagerResponse.GetPasskeySuccess` else this will return a `GenericCredentialManagerResponse.Error`
223+
3. If there is a RestoreKey present this will return a `GenericCredentialManagerResponse.GetCredentialSuccess` else this will return a `GenericCredentialManagerResponse.Error`
224224

225225

226226
3. Sign in using the found Restore Key

Shrine/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ dependencies {
158158
// Retrofit
159159
implementation(libs.retrofit)
160160
implementation(libs.converter.gson)
161+
implementation(libs.androidx.foundation)
161162

162163
// Testing
163164
testImplementation(libs.junit)

Shrine/app/src/main/java/com/authentication/shrine/CredentialManagerUtils.kt

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import androidx.credentials.GetRestoreCredentialOption
3636
import androidx.credentials.exceptions.CreateCredentialException
3737
import androidx.credentials.exceptions.GetCredentialCancellationException
3838
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
39+
import com.authentication.shrine.repository.SERVER_CLIENT_ID
40+
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
3941
import org.json.JSONObject
4042
import javax.inject.Inject
4143

@@ -49,14 +51,14 @@ class CredentialManagerUtils @Inject constructor(
4951
) {
5052

5153
/**
52-
* Retrieves a passkey or password from the credential manager.
54+
* Retrieves a passkey or password credential from the credential manager.
5355
*
5456
* @param publicKeyCredentialRequestOptions The public key credential request options.
5557
* @param context The activity context from the Composable, to be used in Credential Manager APIs
56-
* @return The [GetCredentialResponse] object containing the passkey or password, or null if an
57-
* error occurred.
58+
* @return The [GenericCredentialManagerResponse] object containing the passkey or password, or
59+
* null if an error occurred.
5860
*/
59-
suspend fun getPasskeyOrPassword(
61+
suspend fun getPasskeyOrPasswordCredential(
6062
publicKeyCredentialRequestOptions: JSONObject,
6163
context: Context,
6264
): GenericCredentialManagerResponse {
@@ -86,7 +88,39 @@ class CredentialManagerUtils @Inject constructor(
8688
} catch (e: Exception) {
8789
return GenericCredentialManagerResponse.Error(errorMessage = e.message ?: "")
8890
}
89-
return GenericCredentialManagerResponse.GetPasskeySuccess(getPasskeyResponse = result)
91+
return GenericCredentialManagerResponse.GetCredentialSuccess(getCredentialResponse = result)
92+
}
93+
94+
/**
95+
* Retrieves a Sign in with Google credential from the credential manager.
96+
*
97+
* @param context The activity context from the Composable, to be used in Credential Manager
98+
* APIs
99+
* @return The [GenericCredentialManagerResponse] object containing the passkey or password, or
100+
* null if an error occurred.
101+
*/
102+
suspend fun getSignInWithGoogleCredential(context: Context): GenericCredentialManagerResponse {
103+
val result: GetCredentialResponse?
104+
try {
105+
val credentialRequest = GetCredentialRequest(
106+
listOf(
107+
GetGoogleIdOption.Builder()
108+
.setServerClientId(SERVER_CLIENT_ID)
109+
.setFilterByAuthorizedAccounts(false)
110+
.build(),
111+
)
112+
)
113+
result = credentialManager.getCredential(context, credentialRequest)
114+
} catch (e: GetCredentialCancellationException) {
115+
// When credential selector bottom-sheet is cancelled
116+
return GenericCredentialManagerResponse.CancellationError
117+
} catch (e: GetPublicKeyCredentialDomException) {
118+
// When the user verification / biometric bottom sheet is cancelled
119+
return GenericCredentialManagerResponse.CancellationError
120+
} catch (e: Exception) {
121+
return GenericCredentialManagerResponse.Error(errorMessage = e.message ?: "")
122+
}
123+
return GenericCredentialManagerResponse.GetCredentialSuccess(getCredentialResponse = result)
90124
}
91125

92126
/**
@@ -205,7 +239,7 @@ class CredentialManagerUtils @Inject constructor(
205239
} catch (e: Exception) {
206240
return GenericCredentialManagerResponse.Error(errorMessage = e.message ?: "")
207241
}
208-
return GenericCredentialManagerResponse.GetPasskeySuccess(result)
242+
return GenericCredentialManagerResponse.GetCredentialSuccess(result)
209243
}
210244

211245
/**
@@ -218,8 +252,8 @@ class CredentialManagerUtils @Inject constructor(
218252
}
219253

220254
sealed class GenericCredentialManagerResponse {
221-
data class GetPasskeySuccess(
222-
val getPasskeyResponse: GetCredentialResponse,
255+
data class GetCredentialSuccess(
256+
val getCredentialResponse: GetCredentialResponse,
223257
) : GenericCredentialManagerResponse()
224258

225259
data class CreatePasskeySuccess(

Shrine/app/src/main/java/com/authentication/shrine/api/AuthApiService.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.authentication.shrine.api
1717

18+
import com.authentication.shrine.model.FederationOptionsRequest
1819
import com.authentication.shrine.model.GenericAuthResponse
1920
import com.authentication.shrine.model.PasskeysList
2021
import com.authentication.shrine.model.PasswordRequest
@@ -23,6 +24,7 @@ import com.authentication.shrine.model.RegisterRequestResponse
2324
import com.authentication.shrine.model.RegisterResponseRequestBody
2425
import com.authentication.shrine.model.SignInRequestResponse
2526
import com.authentication.shrine.model.SignInResponseRequest
27+
import com.authentication.shrine.model.SignInWithGoogleRequest
2628
import com.authentication.shrine.model.UsernameRequest
2729
import retrofit2.Response
2830
import retrofit2.http.Body
@@ -155,4 +157,26 @@ interface AuthApiService {
155157
@Header("Cookie") cookie: String,
156158
@Query("credId") credentialId: String,
157159
): Response<GenericAuthResponse>
160+
161+
/**
162+
* Send a request to the server with urls parameter that contains a list of IdPs in an array.
163+
* e.g. urls=["https://accounts.google.com"]. The response will contain a nonce and IdP details.
164+
* @param urls a list of urls to send for federated requests.
165+
*/
166+
@POST("federation/options")
167+
suspend fun getFederationOptions(
168+
@Body urls: FederationOptionsRequest
169+
): Response<GenericAuthResponse>
170+
171+
/**
172+
* Verifies a session ID token and Credential Manager credentials with the backend server.
173+
* @param cookie The session cookie for authentication containing the session ID.
174+
* @param requestParams The parameters to send to the server, including Credential Manager
175+
* credentials.
176+
*/
177+
@POST("federation/verifyIdToken")
178+
suspend fun verifyIdToken(
179+
@Header("Cookie") cookie: String,
180+
@Body requestParams: SignInWithGoogleRequest,
181+
): Response<GenericAuthResponse>
158182
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.authentication.shrine.model
2+
3+
/**
4+
* Represents the request body for getting federation options from the server.
5+
* @param urls a list of urls to send for federated requests.
6+
*/
7+
data class FederationOptionsRequest(
8+
val urls: List<String> = listOf("https://accounts.google.com")
9+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.authentication.shrine.model
2+
3+
/**
4+
* Represents the request body for signing in with Google.
5+
* @param token the auth token retrieved from Credential Manager.
6+
* @param url a fixed url to send for Sign in with Google requests.
7+
*/
8+
data class SignInWithGoogleRequest(
9+
val token: String,
10+
val url: String = "https://accounts.google.com"
11+
)

0 commit comments

Comments
 (0)