Skip to content

Commit c756214

Browse files
sirkerclaude
andcommitted
[Auth] Handle PAR failures gracefully to continue authentication without metadata
Modified AuthProvider to allow authentication to continue when Pushed Authorization Request (PAR) fails. Previously, PAR failures would throw an exception and block the entire authentication flow. Now, if PAR fails (due to network errors or server issues), authentication proceeds without user metadata pre-fill, improving user experience and system resilience. Changes: - Wrapped PAR request in try-catch to handle errors gracefully - Return empty PARResponse on failure instead of throwing exception - Added comprehensive unit tests for PAR error scenarios - Verified existing tests still pass (regression testing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cce5a48 commit c756214

File tree

2 files changed

+101
-10
lines changed

2 files changed

+101
-10
lines changed

authentication/src/main/kotlin/com/uber/sdk2/auth/internal/AuthProvider.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,26 @@ class AuthProvider(
9292

9393
private suspend fun sendPushedAuthorizationRequest(ssoConfig: SsoConfig) =
9494
authContext.prefillInfo?.let {
95-
val response =
96-
authService.loginParRequest(
97-
ssoConfig.clientId,
98-
RESPONSE_TYPE,
99-
Base64Util.encodePrefillInfoToString(it),
100-
ssoConfig.scope ?: "profile",
101-
)
102-
val body = response.body()
103-
body?.takeIf { response.isSuccessful }
104-
?: throw AuthException.ServerError("Bad response ${response.code()}")
95+
try {
96+
val response =
97+
authService.loginParRequest(
98+
ssoConfig.clientId,
99+
RESPONSE_TYPE,
100+
Base64Util.encodePrefillInfoToString(it),
101+
ssoConfig.scope ?: "profile",
102+
)
103+
val body = response.body()
104+
body?.takeIf { response.isSuccessful }
105+
?: run {
106+
// PAR request failed, but continue authentication without metadata
107+
// User can still login, just without pre-filled information
108+
PARResponse("", "")
109+
}
110+
} catch (e: Exception) {
111+
// PAR request failed due to network or other error
112+
// Continue authentication without metadata for better user experience
113+
PARResponse("", "")
114+
}
105115
} ?: PARResponse("", "")
106116

107117
private fun getQueryParams(parResponse: PARResponse) = buildMap {

authentication/src/test/kotlin/com/uber/sdk2/auth/internal/AuthProviderTest.kt

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,85 @@ class AuthProviderTest : RobolectricTestBase() {
200200
assert(result is AuthResult.Success)
201201
assert((result as AuthResult.Success).uberToken.authCode == "authCode")
202202
}
203+
204+
@Test
205+
fun `test authenticate when PAR request fails should continue without metadata`() = runTest {
206+
whenever(ssoLink.execute(any())).thenReturn("authCode")
207+
// Mock PAR request to return error response (e.g., 500)
208+
val errorResponse: Response<PARResponse> = Response.error(500, mock())
209+
whenever(authService.loginParRequest(any(), any(), any(), any())).thenReturn(errorResponse)
210+
val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber")
211+
val authContext =
212+
AuthContext(
213+
AuthDestination.CrossAppSso(listOf(CrossApp.Rider)),
214+
AuthType.AuthCode,
215+
prefillInfo,
216+
)
217+
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
218+
val argumentCaptor = argumentCaptor<Map<String, String>>()
219+
val result = authProvider.authenticate()
220+
// Verify PAR request was attempted
221+
verify(authService).loginParRequest(any(), any(), any(), any())
222+
// Verify authentication continued without request_uri
223+
verify(ssoLink).execute(argumentCaptor.capture())
224+
assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not())
225+
// Verify authentication succeeded
226+
assert(result is AuthResult.Success)
227+
assert((result as AuthResult.Success).uberToken.authCode == "authCode")
228+
}
229+
230+
@Test
231+
fun `test authenticate when PAR request throws exception should continue without metadata`() =
232+
runTest {
233+
whenever(ssoLink.execute(any())).thenReturn("authCode")
234+
// Mock PAR request to throw network exception
235+
whenever(authService.loginParRequest(any(), any(), any(), any()))
236+
.thenThrow(RuntimeException("Network error"))
237+
val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber")
238+
val authContext =
239+
AuthContext(
240+
AuthDestination.CrossAppSso(listOf(CrossApp.Rider)),
241+
AuthType.AuthCode,
242+
prefillInfo,
243+
)
244+
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
245+
val argumentCaptor = argumentCaptor<Map<String, String>>()
246+
val result = authProvider.authenticate()
247+
// Verify PAR request was attempted
248+
verify(authService).loginParRequest(any(), any(), any(), any())
249+
// Verify authentication continued without request_uri
250+
verify(ssoLink).execute(argumentCaptor.capture())
251+
assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not())
252+
// Verify authentication succeeded
253+
assert(result is AuthResult.Success)
254+
assert((result as AuthResult.Success).uberToken.authCode == "authCode")
255+
}
256+
257+
@Test
258+
fun `test authenticate with PKCE when PAR fails should still include code challenge`() = runTest {
259+
whenever(ssoLink.execute(any())).thenReturn("authCode")
260+
whenever(codeVerifierGenerator.generateCodeVerifier()).thenReturn("verifier")
261+
whenever(codeVerifierGenerator.generateCodeChallenge("verifier")).thenReturn("challenge")
262+
whenever(authService.token(any(), any(), any(), any(), any()))
263+
.thenReturn(Response.success(UberToken(accessToken = "accessToken")))
264+
// Mock PAR request to fail
265+
val errorResponse: Response<PARResponse> = Response.error(500, mock())
266+
whenever(authService.loginParRequest(any(), any(), any(), any())).thenReturn(errorResponse)
267+
val prefillInfo = PrefillInfo("email", "firstName", "lastName", "phoneNumber")
268+
val authContext =
269+
AuthContext(AuthDestination.CrossAppSso(listOf(CrossApp.Rider)), AuthType.PKCE(), prefillInfo)
270+
val authProvider = AuthProvider(activity, authContext, authService, codeVerifierGenerator)
271+
val argumentCaptor = argumentCaptor<Map<String, String>>()
272+
val result = authProvider.authenticate()
273+
// Verify PAR request was attempted
274+
verify(authService).loginParRequest(any(), any(), any(), any())
275+
// Verify authentication continued with code challenge but without request_uri
276+
verify(ssoLink).execute(argumentCaptor.capture())
277+
assert(argumentCaptor.lastValue.containsKey(REQUEST_URI).not())
278+
assert(argumentCaptor.lastValue[CODE_CHALLENGE_PARAM] == "challenge")
279+
assert(argumentCaptor.lastValue[CODE_CHALLENGE_METHOD] == CODE_CHALLENGE_METHOD_VAL)
280+
// Verify authentication succeeded
281+
assert(result is AuthResult.Success)
282+
assert((result as AuthResult.Success).uberToken.accessToken == "accessToken")
283+
}
203284
}

0 commit comments

Comments
 (0)