Skip to content

Commit 5d30739

Browse files
somalayaCopilot
andauthored
Handle openid-vc urls in webview, Fixes AB#3535641 (#3013)
**Context** : Total loss recovery is a feature introduced by VID team to recover an account. This feature will be triggered from inside of a webview when the user clicks on "Recover your account" link. After this a 3P identity verifier will verify the user and provide a credential. Once a credential is available, a specific redirect URI is triggered **openid-vc://**? request_uri=https://verifier.example.com/request/123, which will invoke wallet library in Authenticator app --- All this is functionality not connected to broker except the fact that the URLs are all being loaded in our webview. **What is openid-vc** ? : The openid-vc:// scheme is part of the OpenID4VC family of specifications, which includes: OpenID4VCI (Verifiable Credential Issuance) — how a wallet receives credentials from an issuer OpenID4VP (Verifiable Presentations) — how a wallet presents credentials to a verifier **Problem** : When a web page loaded in the authentication WebView navigates to an openid-vc:// URI (used by OpenID Verifiable Credentials flows), the WebView cannot handle this non-HTTP custom scheme natively. The navigation fails, blocking the VC issuance/presentation flow. **Solution** : Intercept **openid-vc://** navigations in AzureActiveDirectoryWebViewClient.handleUrl() and delegate them to the Android system so a registered wallet app can handle the request. Fixes [AB#3535641](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3535641) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent de46f30 commit 5d30739

6 files changed

Lines changed: 107 additions & 0 deletions

File tree

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ vNext
55
- [MINOR] Edge TB: Claims (#2925)
66
- [PATCH] Update Moshi to 1.15.2 to resolve okio CVE-2023-3635 vulnerability (#3005)
77
- [MINOR] Handle target="_blank" links in authorization WebView (#3010)
8+
- [MINOR] Handle openid-vc urls in webview (#3013)
89

910
Version 24.0.1
1011
----------

common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,13 @@ public static String computeMaxHostBrokerProtocol() {
13121312
*/
13131313
public static final String AMAZON_APP_REDIRECT_PREFIX = "aea://";
13141314

1315+
/**
1316+
* Custom URI scheme prefix for OpenID Verifiable Credentials.
1317+
* WebView cannot load this scheme natively; it must be intercepted and
1318+
* forwarded to an external handler (wallet app) via an ACTION_VIEW intent.
1319+
*/
1320+
public static final String OPENID_VC_SCHEME_PREFIX = "openid-vc://";
1321+
13151322
/**
13161323
* Prefix for the Authenticator MFA linking.
13171324
*/

common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ else if (isRedirectUrl(formattedURL)) {
338338
} else if (isAmazonAppRedirect(formattedURL)) {
339339
Logger.info(methodTag, "It is an Amazon app request");
340340
processAmazonAppUri(url);
341+
} else if (isOpenIdVcUrl(formattedURL)) {
342+
// TO-DO : Decide whether flight is needed in this case. https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3541420
343+
Logger.info(methodTag, "It is an OpenID Verifiable Credentials request.");
344+
processOpenIdVcRequest(view, url);
341345
} else if (isInvalidRedirectUri(url)) {
342346
Logger.info(methodTag,"Check for Redirect Uri.");
343347
processInvalidRedirectUri(view, url);
@@ -496,6 +500,18 @@ private boolean isAmazonAppRedirect(@NonNull final String url) {
496500
return url.startsWith(AMAZON_APP_REDIRECT_PREFIX);
497501
}
498502

503+
/**
504+
* Checks if the URL uses the openid-vc:// custom scheme.
505+
* This scheme is used by OpenID Verifiable Credentials flows and must be
506+
* intercepted so a registered wallet app can handle it.
507+
*
508+
* @param url The lowercase URL to check.
509+
* @return true if the URL starts with the openid-vc:// scheme.
510+
*/
511+
private boolean isOpenIdVcUrl(@NonNull final String url) {
512+
return url.startsWith(AuthenticationConstants.Broker.OPENID_VC_SCHEME_PREFIX);
513+
}
514+
499515
private boolean isWebCpEnrollmentUrl(@NonNull final String url) {
500516
return url.startsWith(AuthenticationConstants.Broker.WEBCP_ENROLLMENT_URL);
501517
}
@@ -901,6 +917,43 @@ private void processAmazonAppUri(@NonNull final String url) {
901917
Logger.info(methodTag, "Sent Intent to launch Amazon app");
902918
}
903919

920+
/**
921+
* Handles an openid-vc:// URL by stopping the WebView and launching an
922+
* {@link Intent#ACTION_VIEW} intent so the system can route it to the
923+
* registered wallet application.
924+
*
925+
* @param view The WebView that intercepted the navigation.
926+
* @param url The original (non-lowercased) openid-vc:// URL.
927+
*/
928+
private void processOpenIdVcRequest(@NonNull final WebView view, @NonNull final String url) {
929+
final String methodTag = TAG + ":processOpenIdVcRequest";
930+
view.stopLoading();
931+
final Span span = createSpanWithAttributesFromParent(SpanName.ProcessOpenIdVcRequest.name());
932+
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
933+
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
934+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
935+
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
936+
getActivity().startActivity(intent);
937+
Logger.info(methodTag, "Launched external handler for OpenID VC request.");
938+
span.setAttribute(AttributeName.is_openid_vc_handler_found.name(), true);
939+
span.setStatus(StatusCode.OK);
940+
} else {
941+
Logger.warn(methodTag, "No application found to handle openid-vc:// URI.");
942+
span.setAttribute(AttributeName.is_openid_vc_handler_found.name(), false);
943+
span.setStatus(StatusCode.ERROR, "No handler found for openid-vc:// URI");
944+
returnError(ErrorStrings.ACTIVITY_NOT_FOUND, "No application found to handle the OpenID Verifiable Credentials request.");
945+
}
946+
} catch (final ActivityNotFoundException e) {
947+
Logger.error(methodTag, "Failed to launch handler for openid-vc:// URI.", e);
948+
span.setAttribute(AttributeName.is_openid_vc_handler_found.name(), false);
949+
span.recordException(e);
950+
span.setStatus(StatusCode.ERROR, "Failed to launch handler for openid-vc:// URI");
951+
returnError(ErrorStrings.ACTIVITY_NOT_FOUND, "Failed to launch handler for the OpenID Verifiable Credentials request.");
952+
} finally {
953+
span.end();
954+
}
955+
}
956+
904957
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
905958
protected void openLinkInBrowser(final String url) {
906959
final String methodTag = TAG + ":openLinkInBrowser";

common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public class AzureActiveDirectoryWebViewClientTest {
126126
private static final String TEST_WEB_CP_ENROLLMENT_URL = "https://enterprise.google.com/android/enroll";
127127

128128
private static final String TEST_PLAYSTORE_REDIRECT_WITH_BROWSER_PROTOCOL = "browser://play.app.goo.gl/?link=https://play.google.com/store/apps/details?id=com.microsoft.windowsintune.companyportal";
129+
private static final String TEST_OPENID_VC_URL = "openid-vc://credential-offer?credential_issuer=https%3A%2F%2Fexample.com&credential_configuration_ids=VerifiedEmployee";
130+
129131
@Before
130132
public void setup() throws ClientException {
131133
mContext = ApplicationProvider.getApplicationContext();
@@ -190,6 +192,44 @@ public void testUrlOverrideHandlesWebsiteRequestUrl() {
190192
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_PLAYSTORE_REDIRECT_WITH_BROWSER_PROTOCOL));
191193
}
192194

195+
@Test
196+
public void testUrlOverrideHandlesOpenIdVcUrl() {
197+
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_OPENID_VC_URL));
198+
}
199+
200+
@Test
201+
public void testOpenIdVcUrl_StopsWebViewAndReturnsError_WhenNoHandlerFound() {
202+
// Arrange
203+
final IAuthorizationCompletionCallback mockCallback = Mockito.mock(IAuthorizationCompletionCallback.class);
204+
final ArgumentCaptor<RawAuthorizationResult> resultCaptor = ArgumentCaptor.forClass(RawAuthorizationResult.class);
205+
final AzureActiveDirectoryWebViewClient webViewClient = new AzureActiveDirectoryWebViewClient(
206+
mActivity,
207+
mockCallback,
208+
url -> {},
209+
TEST_REDIRECT_URI,
210+
Mockito.mock(SwitchBrowserRequestHandler.class),
211+
"homeTenantId",
212+
false
213+
);
214+
final WebView mockWebView = Mockito.mock(WebView.class);
215+
216+
// Act - Robolectric has no handler registered for openid-vc://, so the no-handler path executes
217+
final boolean result = webViewClient.shouldOverrideUrlLoading(mockWebView, TEST_OPENID_VC_URL);
218+
219+
// Assert
220+
assertTrue("shouldOverrideUrlLoading must return true for openid-vc:// URLs", result);
221+
Mockito.verify(mockWebView).stopLoading();
222+
223+
// Verify the callback received an error result
224+
Mockito.verify(mockCallback).onChallengeResponseReceived(resultCaptor.capture());
225+
final RawAuthorizationResult capturedResult = resultCaptor.getValue();
226+
assertEquals("Expected ACTIVITY_NOT_FOUND error code",
227+
ErrorStrings.ACTIVITY_NOT_FOUND,
228+
((ClientException) capturedResult.getException()).getErrorCode());
229+
assertTrue("Expected error message about no application found",
230+
capturedResult.getException().getMessage().contains("No application found"));
231+
}
232+
193233
@Test
194234
@Config(shadows = {
195235
ShadowProcessUtil.class})

common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,11 @@ public enum AttributeName {
607607
* The time (in milliseconds) spent on secret key serialization/deserialization.
608608
*/
609609
secret_key_serialization_duration,
610+
611+
/**
612+
* Indicates if an external handler was found to handle the openid-vc:// URI.
613+
*/
614+
is_openid_vc_handler_found,
610615

611616
//endregion
612617

common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public enum SpanName {
7171
GetAllSsoTokens,
7272
ProcessWebCpEnrollmentRedirect,
7373
ProcessWebCpAuthorizeUrlRedirect,
74+
ProcessOpenIdVcRequest,
7475
PasskeyWebListener,
7576
InstallCertOnWpj,
7677
/**

0 commit comments

Comments
 (0)