Improving Android WebView Identity Verification Errors

Improving Android WebView Identity Verification Errors

1. Work Background

This work was not a case of designing an identity verification feature from scratch, but rather a maintenance case involving the analysis and correction of an error that occurred in an existing feature. The project provided web screens implemented in Vue through a shared WebView in a React Native app, and N*** CheckPlus identity verification was already integrated into the screen.

The feature worked normally in web browsers and the iOS app, but authentication failed only in the React Native Android WebView. The scope was not to completely change the existing integration structure, but to narrow down the cause based on platform-specific differences and restore the authentication flow while minimizing the impact on other features of the shared WebView.

2. Error Occurrence and Clues from the Analysis

2.1 Confirmed Error

When N*** identity verification was performed in the Android app's WebView, authentication did not complete and the user was redirected to a failure page. The following error was recorded at the time.

SecurityError: Failed to read a named property 'checkSuccess' from 'Window':
Blocked a frame with origin "https://n***.checkplus.co.kr"
from accessing a cross-origin frame.

This message means that an attempt to directly access a property of the other window between Windows or Frames from different origins was blocked by the Same-Origin Policy. However, checkSuccesswas not a function written in the project itself, but a name used inside the N*** authentication screen, so it was not possible to approach the issue by directly modifying that function in the project's Vue code.

In addition, I did not assume that the exception displayed last on the screen was the initial cause of the failure. The authentication flow may already have failed at an earlier stage, after which a Cross-Origin exception could have occurred while a subsequent script on the failure page was executing.

2.2 Comparison of Execution Environments and URL Navigation Flows

When the same feature was compared across execution environments, it proceeded normally in web browsers, the regular browser on the same Android device, and the iOS app, and failed only in the React Native Android WebView. Based on this difference, I first examined the Android WebView's URL handling flow rather than the N*** service as a whole or the Vue business logic.

The following URL navigation flow was confirmed in the records immediately before the failure.

/cert/mobileCert/main
> /cert/mobileCert/method
> /cert/mobileCert/fail/applink
> SecurityError 발생

SecurityErrorThe key clue was that the flow passed through the /fail/applink path before that point. This record alone was not sufficient to determine the exact cause of the failure inside N***, but it was necessary to check whether the external authentication app invocation step that preceded the final Cross-Origin exception had failed first.

3. Reviewing the Existing Code and Narrowing the Scope of the Cause

3.1 Identifying the Actual Modification Point

Because the issue occurred only on Android, I initially considered whether the Java or Kotlin WebViewClient needed to be modified. However, the project's Android Activity did not directly create the WebView; the actual screen was built in a shared TypeScript component that wrapped react-native-webview.

Therefore, the modification point was the shared React Native WebView component, not the Android Activity. Even when an error occurs on Android, it is necessary to first determine which layer of the project controls native functionality in order to reduce the scope of unnecessary changes.

3.2 Separating Existing Settings and the In-House Callback Flow

The existing shared WebView already had originWhitelist={['*']}, JavaScript, DOM Storage, multi-window, and platform-specific cookie settings applied. Since, for Android, settings such as thirdPartyCookiesEnabled, domStorageEnabled, and setSupportMultipleWindows were already present, it was difficult to regard a simple missing cookie or storage option as the primary cause.

originWhitelist specifies the range of navigation targets that the WebView allows; it does not disable the Same-Origin Policy or guarantee that an external app will launch. Therefore, rather than determining the cause based solely on the settings, I narrowed the scope based on the fact that the shared component examined at the time had no handler that explicitly branched on external app URLs.

The project also had a separate flow in which a PC popup result page called window.opener.callbackEncodeData(). In contrast, the mobile app submitted the form with _self and processed EncodeData on the page it returned to, while the function name shown in this error was checkSuccess. Therefore, I did not conflate the PC popup callback issue with the Android WebView external app invocation issue as having the same cause.

I also considered changing the multi-window settings, but did not apply it to the actual solution. Only measures whose normal operation was verified were included in the resolution.

3.3 Confirming Handling of External App Custom Schemes

When launching an external authentication app such as P*** during the N*** identity verification process, the URL is not a conventional http:// or https:// address, but rather aintent: URI or tauthlink: custom schemes can be used. The work notes from that time also retained a form such as tauthlink://sktauth?... excluding sensitive parameters.

A typical mobile browser can pass such URLs to the operating system's app-launch flow, but in an in-app WebView, it may be necessary to intercept URL navigation requests and pass them to the React Native or native layer.

The common WebView component reviewed at the time had settings related to cookies and windows, but it did not have a handler that explicitly distinguished authentication app URLs from HTTP URLs and passed them to an external app. Based on platform-specific reproduction results, the /fail/applink navigation record, and omissions identified in the existing code, we first supplemented handling for Android external authentication app URLs.

4. Applying the solution

4.1 Handling criteria

onShouldStartLoadWithRequestwas used to inspect URL navigation requests occurring during authentication and separate requests that should continue to be handled by the WebView from external app requests that should be passed to the operating system. Because the URL in this issue was a navigation request occurring during the authentication process rather than the initial load, this approach could be used to address it.

The handling criteria were organized as follows.

  • http://, https://, about:blankcontinue to be handled by the WebView.

  • Only authentication app URLs verified on Android (intent:, tauthlink:) are allowed as external app launch targets.

  • For URLs passed to an external app, return falseso that the WebView does not load them again.

  • Schemes not included in the allowlist are not executed, and WebView loading is also stopped.

  • On iOS, this handler does not make a separate Linking call.

4.2 Core code

The code below is a simplified example intended to make it easier to understand the core handling structure applied at the time.

const handleShouldStartLoadWithRequest = (
  {url}: {url: string},
): boolean => {
  if (/^(https?:\/\/|about:blank(?:#.*)?$)/i.test(url)) return true;
  if (Platform.OS !== 'android') return true;

  const isIntentUrl = /^intent:/i.test(url);
  const isTAuthUrl = /^tauthlink:/i.test(url);
  if (!isIntentUrl && !isTAuthUrl) return false;

  const targetUrl = isIntentUrl ? parseIntentUrl(url) : url;
  if (!targetUrl) {
    showAuthAppError();
    return false;
  }

  void Linking.openURL(targetUrl).catch(showAuthAppError);
  return false;
};

The key is to separate regular web URLs from allowed Android authentication app URLs, request external app execution with Linking.openURL(), and then stop the WebView from performing that navigation.

parseIntentUrl()is a helper function that constructs the actual app-scheme URL from the intent: format reviewed at the time. The notes from that time also included a separate branch that checked getFallbackUrl() for S.browser_fallback_url when app execution failed. The example above retains only the core structure for separating URL navigation; the two functions are not general-purpose parsers for all Android Intent URIs, so their scope of use should be restricted to the formats verified in the actual service.

For the pure custom scheme tauthlink:, there may be no app package or fallback URL information. Therefore, whether to display only a notification when execution fails or manage scheme-to-package mappings and redirect users to a store must be decided as a separate policy.

If the common component passes external WebView properties through ...rest, you should check whether an existing onShouldStartLoadWithRequestis present. Rather than simply overwriting one with the other based on property order, it is safer to combine the return results of the existing handler and the common handler.

5. Retest results

After the modification at the time, we retried N*** identity verification in the Android environment where it had previously failed. The regular https:// authentication page continued to open in the WebView, while the authentication app's custom scheme was not loaded directly by the WebView but was passed as an external app launch request. As a result, calls to external authentication apps such as P*** and the N*** identity verification process continued, and the existing /fail/applink and SecurityErrorThe failure flow in which they were followed by one another was no longer reproduced in the same scenario.

This result does not mean that the custom scheme handling logic changed the same-origin policy. As the external app call proceeded through the normal path, the process no longer entered the failure path where the subsequent script for /fail/applink was executed, and as a result, the following Cross-Origin exception also did not appear.

However, I did not conclude that the omission of custom scheme handling was the sole internal cause of SecurityError. This was because I had not directly verified the entire structure in which checkSuccess was called within N***. The facts that could be confirmed from the records at the time were as follows.

  • Authentication failed only in the WebView of the Android app.

  • When it failed, /fail/applink was accessed, followed by SecurityError.

  • The common WebView examined at the time had no logic that explicitly handled external authentication app URLs.

  • After this handling was added, the same authentication scenario returned to normal.

Therefore, it is more accurate to summarize this case not as “the error was removed by bypassing the Cross-Origin policy,” but as “after supplementing the external authentication app URL handling logic in the Android WebView, the preceding failure path and the subsequent Cross-Origin exception were no longer reproduced in the same scenario.”

6. Considerations for implementation

Adding a URL navigation handler to the common WebView may cause screens other than N*** to pass through the same logic. Forwarding every URL that is not HTTP to an external app may result in unintended app launches or changes to existing functionality, so it is safer to limit the scope to authentication screens or authentication-in-progress states and allow only verified schemes.

intent: If URI support must be provided generically, the actual internal schemes, packages, and fallback URL domains should also be validated against an allowlist. If the full authentication URL is recorded in logs, it may contain identifiers or tokens, so only necessary information such as the scheme and host should be recorded, and sensitive parameters should be masked.

7. Experience gained from the work

7.1 The flow preceding the final error must be examined

The first message I identified was a Cross-Origin SecurityError, but the URL navigation history showed that the app had previously moved to the /fail/applink path. If I had tried to fix only the final exception directly, I might have overlooked the indication that the external app call had failed first. In incident analysis, URL navigation and state changes before the exception is reached must also be examined.

7.2 Platform comparisons can narrow the scope of analysis

The fact that the issue worked normally in web browsers and the iOS app but failed only in the React Native Android WebView allowed me to quickly narrow the analysis target. Comparing the regular browser and the app WebView on the same Android device also helped distinguish differences in the operating system itself from differences in the WebView integration approach.

7.3 The implemented measure must be distinguished from the alternatives considered

During the analysis, I considered several candidates, including window handling methods and multi-window settings. However, the key change that was confirmed to restore normal operation was the handling that distinguished permitted external app URLs in onShouldStartLoadWithRequest and forwarded them to Linking. When documenting a technical case, distinguishing measures that were actually implemented and verified from alternatives that were merely considered as possibilities helps prevent the results from being overstated.

8. Conclusion

This work was a case of analyzing and improving an N*** identity verification error that occurred only in the Android WebView of an existing React Native hybrid app. Although the investigation began with a Cross-Origin error, I narrowed the analysis to external authentication app URL handling by sequentially examining platform-specific reproduction results, the URL navigation flow immediately before the failure, the existing common WebView settings, and the custom callback structure.

Based on the records from the time, I supplemented the implementation to distinguish regular web URLs from Android authentication app URLs and forward permitted schemes to React Native Linking. After the fix, the external authentication app call and N*** identity verification proceeded normally in the same failure scenario, and /fail/applink and the subsequent SecurityError were no longer reproduced.

This case taught me that rather than directly fixing only the last error displayed on the screen, it is important to check the URL navigation before the error and what handling was actually missing at the web-to-native boundary. It was also important to distinguish verified facts from inferences so as not to overstate the conclusion of a technical case.

green

Site footer